diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-12-14 18:47:08 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-12-14 18:47:08 +0100 |
commit | ff0b4082e7d328b5cf5dfe0e79d27471c3afdb50 (patch) | |
tree | 865e46af8d4c406418050383a4e25c761b2dce3d | |
parent | 597f59619a0e08f75a0e60ac09da7e5aea538404 (diff) | |
parent | b27bada7c6b22d83557c6592d8c00fea2a6c35fe (diff) |
Merge with master
878 files changed, 15329 insertions, 12677 deletions
diff --git a/annotations/pom.xml b/annotations/pom.xml index 146eb5e9be2..3321dddaf8c 100644 --- a/annotations/pom.xml +++ b/annotations/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>annotations</artifactId> <packaging>jar</packaging> diff --git a/application-deploy-plugin/pom.xml b/application-deploy-plugin/pom.xml index 3cae7cf7831..501dee385a6 100644 --- a/application-deploy-plugin/pom.xml +++ b/application-deploy-plugin/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>application-deploy-plugin</artifactId> <version>6-SNAPSHOT</version> diff --git a/application-model/pom.xml b/application-model/pom.xml index 13a2d3ea49b..801c72e9194 100644 --- a/application-model/pom.xml +++ b/application-model/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>application-model</artifactId> <packaging>container-plugin</packaging> diff --git a/application-preprocessor/pom.xml b/application-preprocessor/pom.xml index 4fa319a881a..6ffff09e548 100644 --- a/application-preprocessor/pom.xml +++ b/application-preprocessor/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>application-preprocessor</artifactId> <packaging>jar</packaging> diff --git a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java index e879acef3bb..303ce3e8d29 100644 --- a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java +++ b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java @@ -39,9 +39,9 @@ public class ApplicationPreprocessor { FilesApplicationPackage.Builder applicationPackageBuilder = new FilesApplicationPackage.Builder(applicationDir); outputDir.ifPresent(applicationPackageBuilder::preprocessedDir); ApplicationPackage preprocessed = applicationPackageBuilder.build().preprocess( - new Zone(environment.orElse(Environment.defaultEnvironment()), region.orElse(RegionName.defaultName())), - (a, b) -> { - }, logger); + ZoneId.from(environment.orElse(Environment.defaultEnvironment()), region.orElse(RegionName.defaultName())), + (a, b) -> { }, + logger); preprocessed.validateXML(); } diff --git a/application/pom.xml b/application/pom.xml index 17e9ece3543..8103226e55d 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>application</artifactId> <packaging>jar</packaging> diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml index c87589d7be2..bfd02d54d43 100644 --- a/athenz-identity-provider-service/pom.xml +++ b/athenz-identity-provider-service/pom.xml @@ -11,6 +11,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <dependencies> <!-- COMPILE --> diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java index 7910650ed5e..706f797cd2c 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslKeyStoreConfigurator.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; -import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator; import com.yahoo.jdisc.http.ssl.SslKeyStoreContext; @@ -11,12 +10,15 @@ import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.AthenzCertificateClient; +import java.io.IOException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; -import java.time.Instant; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -35,13 +37,14 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements // TODO Make expiry and update frequency configurable parameters private static final Duration CERTIFICATE_EXPIRY_TIME = Duration.ofDays(30); private static final Duration CERTIFICATE_UPDATE_PERIOD = Duration.ofDays(7); + private static final String DUMMY_PASSWORD = "athenz"; private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final AthenzCertificateClient certificateClient; private final KeyProvider keyProvider; private final AthenzProviderServiceConfig.Zones zoneConfig; private final AtomicBoolean alreadyConfigured = new AtomicBoolean(); - private final Zone zone; + private KeyStore initialKeyStore; @Inject public AthenzSslKeyStoreConfigurator(KeyProvider keyProvider, @@ -51,20 +54,20 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements this.certificateClient = new AthenzCertificateClient(config, zoneConfig); this.keyProvider = keyProvider; this.zoneConfig = zoneConfig; - this.zone = zone; + this.initialKeyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig); } @Override public void configure(SslKeyStoreContext sslKeyStoreContext) { - // TODO Remove this when main is ready - if (zone.system() != SystemName.cd) { - return; - } if (alreadyConfigured.getAndSet(true)) { // For debugging purpose of SslKeyStoreConfigurator interface throw new IllegalStateException("Already configured. configure() can only be called once."); } - AthenzCertificateUpdater updater = new AthenzCertificateUpdater(sslKeyStoreContext); - scheduler.scheduleAtFixedRate(updater, /*initialDelay*/0, CERTIFICATE_UPDATE_PERIOD.toMinutes(), TimeUnit.MINUTES); + sslKeyStoreContext.updateKeyStore(initialKeyStore, DUMMY_PASSWORD); + initialKeyStore = null; + scheduler.scheduleAtFixedRate(new AthenzCertificateUpdater(sslKeyStoreContext), + CERTIFICATE_UPDATE_PERIOD.toMinutes()/*initial delay*/, + CERTIFICATE_UPDATE_PERIOD.toMinutes(), + TimeUnit.MINUTES); } @Override @@ -77,6 +80,32 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements } } + private static KeyStore downloadCertificate(KeyProvider keyProvider, + AthenzCertificateClient certificateClient, + AthenzProviderServiceConfig.Zones zoneConfig) { + try { + PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); + X509Certificate certificate = certificateClient.updateCertificate(privateKey, CERTIFICATE_EXPIRY_TIME); + verifyActualExpiry(certificate); + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + keyStore.setKeyEntry("athenz", privateKey, DUMMY_PASSWORD.toCharArray(), new Certificate[]{certificate}); + return keyStore; + } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) { + throw new RuntimeException(e); + } + } + + private static void verifyActualExpiry(X509Certificate certificate) { + Duration actualExpiry = + Duration.between(certificate.getNotBefore().toInstant(), certificate.getNotAfter().toInstant()); + if (CERTIFICATE_EXPIRY_TIME.compareTo(actualExpiry) > 0) { + log.log(LogLevel.WARNING, + String.format("Expected expiry %s, got %s", CERTIFICATE_EXPIRY_TIME, actualExpiry)); + } + } + private class AthenzCertificateUpdater implements Runnable { private final SslKeyStoreContext sslKeyStoreContext; @@ -89,29 +118,13 @@ public class AthenzSslKeyStoreConfigurator extends AbstractComponent implements public void run() { try { log.log(LogLevel.INFO, "Updating Athenz certificate from ZTS"); - PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); - X509Certificate certificate = certificateClient.updateCertificate(privateKey, CERTIFICATE_EXPIRY_TIME); - verifyActualExperiy(certificate); - - String dummyPassword = "athenz"; - KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(null); - keyStore.setKeyEntry("athenz", privateKey, dummyPassword.toCharArray(), new Certificate[]{certificate}); - sslKeyStoreContext.updateKeyStore(keyStore, dummyPassword); + KeyStore keyStore = downloadCertificate(keyProvider, certificateClient, zoneConfig); + sslKeyStoreContext.updateKeyStore(keyStore, DUMMY_PASSWORD); log.log(LogLevel.INFO, "Athenz certificate reload successfully completed"); } catch (Throwable e) { log.log(LogLevel.ERROR, "Failed to update certificate from ZTS: " + e.getMessage(), e); } } - private void verifyActualExperiy(X509Certificate certificate) { - Instant notAfter = certificate.getNotAfter().toInstant(); - Instant notBefore = certificate.getNotBefore().toInstant(); - if (!notBefore.plus(CERTIFICATE_EXPIRY_TIME).equals(notAfter)) { - Duration actualExpiry = Duration.between(notBefore, notAfter); - log.log(LogLevel.WARNING, - String.format("Expected expiry %s, got %s", CERTIFICATE_EXPIRY_TIME, actualExpiry)); - } - } } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java new file mode 100644 index 00000000000..8c8b5de2a30 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzSslTrustStoreConfigurator.java @@ -0,0 +1,110 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.athenz.instanceproviderservice; + +import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator; +import com.yahoo.jdisc.http.ssl.SslTrustStoreContext; +import com.yahoo.log.LogLevel; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.logging.Logger; + +/** + * @author bjorncs + */ +public class AthenzSslTrustStoreConfigurator implements SslTrustStoreConfigurator { + + private static final Logger log = Logger.getLogger(AthenzSslTrustStoreConfigurator.class.getName()); + + private static final Provider provider = new BouncyCastleProvider(); + private final KeyStore trustStore; + + @Inject + public AthenzSslTrustStoreConfigurator(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig athenzProviderServiceConfig) { + this.trustStore = createTrustStore(keyProvider, configserverConfig, athenzProviderServiceConfig); + } + + @Override + public void configure(SslTrustStoreContext sslTrustStoreContext) { + sslTrustStoreContext.updateTrustStore(trustStore); + log.log(LogLevel.INFO, "Configured JDisc trust store with self-signed certificate"); + } + + private static KeyStore createTrustStore(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig athenzProviderServiceConfig) { + try { + KeyPair keyPair = getKeyPair(keyProvider, configserverConfig, athenzProviderServiceConfig); + X509Certificate selfSignedCertificate = createSelfSignedCertificate(keyPair, configserverConfig); + log.log(LogLevel.FINE, "Generated self-signed certificate: " + selfSignedCertificate); + KeyStore trustStore = KeyStore.getInstance("JKS"); + try (FileInputStream in = new FileInputStream(athenzProviderServiceConfig.athenzCaTrustStore())) { + trustStore.load(in, "changeit".toCharArray()); + } + trustStore.setCertificateEntry("cfgselfsigned", selfSignedCertificate); + return trustStore; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static KeyPair getKeyPair(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig athenzProviderServiceConfig) { + String key = configserverConfig.environment() + "." + configserverConfig.region(); + AthenzProviderServiceConfig.Zones zoneConfig = athenzProviderServiceConfig.zones(key); + return keyProvider.getKeyPair(zoneConfig.secretVersion()); + } + + private static X509Certificate createSelfSignedCertificate(KeyPair keyPair, ConfigserverConfig config) + throws IOException, CertificateException, OperatorCreationException { + ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate()); + X500Name x500Name = new X500Name("CN="+ config.loadBalancerAddress()); + Instant now = Instant.now(); + Date notBefore = Date.from(now); + Date notAfter = Date.from(now.plus(Duration.ofDays(30))); + + GeneralNames generalNames = new GeneralNames( + config.zookeeperserver().stream() + .map(server -> new GeneralName(GeneralName.dNSName, server.hostname())) + .toArray(GeneralName[]::new)); + + X509v3CertificateBuilder certificateBuilder = + new JcaX509v3CertificateBuilder( + x500Name, BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, x500Name, keyPair.getPublic() + ) + .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)) + .addExtension(Extension.subjectAlternativeName, false, generalNames); + + return new JcaX509CertificateConverter() + .setProvider(provider) + .getCertificate(certificateBuilder.build(contentSigner)); + } + +} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java index a72a2fcbc6c..1d141099428 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/KeyProvider.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.athenz.instanceproviderservice; +import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; @@ -11,4 +12,8 @@ public interface KeyProvider { PrivateKey getPrivateKey(int version); PublicKey getPublicKey(int version); + + default KeyPair getKeyPair(int version) { + return new KeyPair(getPublicKey(version), getPrivateKey(version)); + } } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java index 2dc3f24664c..1014fc4afdf 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSigner.java @@ -2,9 +2,9 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.provision.Zone; import com.yahoo.log.LogLevel; -import com.yahoo.net.HostName; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.KeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import org.bouncycastle.asn1.ASN1ObjectIdentifier; @@ -68,13 +68,16 @@ public class CertificateSigner { private final Clock clock; @Inject - public CertificateSigner(KeyProvider keyProvider, AthenzProviderServiceConfig config, Zone zone) { - this(getPrivateKey(keyProvider, config, zone), HostName.getLocalhost(), Clock.systemUTC()); + public CertificateSigner(KeyProvider keyProvider, + ConfigserverConfig configserverConfig, + AthenzProviderServiceConfig config, + Zone zone) { + this(getPrivateKey(keyProvider, config, zone), configserverConfig.loadBalancerAddress(), Clock.systemUTC()); } - CertificateSigner(PrivateKey caPrivateKey, String configServerHostname, Clock clock) { + CertificateSigner(PrivateKey caPrivateKey, String loadBalancerAddress, Clock clock) { this.caPrivateKey = caPrivateKey; - this.issuer = new X500Name("CN=" + configServerHostname); + this.issuer = new X500Name("CN=" + loadBalancerAddress); this.clock = clock; } diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java index e66131b6cf7..ac8c0eabf31 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java @@ -45,7 +45,8 @@ public class SecretStoreKeyProvider implements KeyProvider { return getKeyPair(version).getPublic(); } - private KeyPair getKeyPair(int version) { + @Override + public KeyPair getKeyPair(int version) { synchronized (secrets) { KeyPair keyPair = secrets.get(version); if (keyPair == null) { diff --git a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def index 13cc78b0bd0..21f2aea6ab0 100644 --- a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def +++ b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def @@ -21,3 +21,6 @@ ztsUrl string # Certificate DNS suffix certDnsSuffix string + +# Path to Athenz CA JKS trust store +athenzCaTrustStore string diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java index c09a9fb1740..da2bf929e82 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/TestUtils.java @@ -25,7 +25,8 @@ public class TestUtils { .zones(ImmutableMap.of(zone.environment().value() + "." + zone.region().value(), zoneConfig)) .certDnsSuffix(dnsSuffix) .ztsUrl("localhost/zts") - .athenzPrincipalHeaderName("Athenz-Principal-Auth")); + .athenzPrincipalHeaderName("Athenz-Principal-Auth") + .athenzCaTrustStore("/dummy/path/to/athenz-ca.jks")); } } diff --git a/bootstrap-cpp.sh b/bootstrap-cpp.sh index 47d2a82622a..e6b4c816065 100755 --- a/bootstrap-cpp.sh +++ b/bootstrap-cpp.sh @@ -33,7 +33,7 @@ mkdir -p "${BUILD_DIR}" || { BUILD_DIR=$(realpath "${BUILD_DIR}") # Build it -source /opt/rh/devtoolset-6/enable || true +source /opt/rh/devtoolset-7/enable || true cd "${SOURCE_DIR}" bash ./bootstrap.sh full cd "${BUILD_DIR}" diff --git a/bootstrap.sh b/bootstrap.sh index 075da74b7c7..b71af73c529 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -44,8 +44,16 @@ $top/dist/getversion.pl -M $top > $top/dist/vtag.map # The 'default' mode also builds some modules needed by C++ code. # The 'full' mode also builds modules needed by C++ tests. -# must install parent pom first: +# must install parent poms first: echo "Downloading all dependencies. This may take a few minutes with an empty Maven cache." +( + cd container-dependency-versions + mvn_install +) +( + cd parent + mvn_install +) mvn_install -N # and build plugins first: diff --git a/bundle-plugin-test/pom.xml b/bundle-plugin-test/pom.xml index 8d8bf48cc07..3dd5be00add 100644 --- a/bundle-plugin-test/pom.xml +++ b/bundle-plugin-test/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa</groupId> <artifactId>bundle-plugin-test</artifactId> diff --git a/bundle-plugin/pom.xml b/bundle-plugin/pom.xml index 02f0ed2ccf5..47a06a1ee76 100644 --- a/bundle-plugin/pom.xml +++ b/bundle-plugin/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>bundle-plugin</artifactId> <version>6-SNAPSHOT</version> diff --git a/chain/pom.xml b/chain/pom.xml index a3096d37748..01a64a93609 100755 --- a/chain/pom.xml +++ b/chain/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>chain</artifactId> <packaging>jar</packaging> diff --git a/clustercontroller-apps/pom.xml b/clustercontroller-apps/pom.xml index 26f466eb208..bdc6059a816 100644 --- a/clustercontroller-apps/pom.xml +++ b/clustercontroller-apps/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>clustercontroller-apps</artifactId> <version>6-SNAPSHOT</version> diff --git a/clustercontroller-apputil/pom.xml b/clustercontroller-apputil/pom.xml index 68adc82596f..42c7690eab6 100644 --- a/clustercontroller-apputil/pom.xml +++ b/clustercontroller-apputil/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>clustercontroller-apputil</artifactId> <version>6-SNAPSHOT</version> diff --git a/clustercontroller-core/pom.xml b/clustercontroller-core/pom.xml index 499dee0d1de..99cf4df643b 100644 --- a/clustercontroller-core/pom.xml +++ b/clustercontroller-core/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>clustercontroller-core</artifactId> <version>6-SNAPSHOT</version> diff --git a/clustercontroller-standalone/pom.xml b/clustercontroller-standalone/pom.xml index f51555a92b6..f435cd9d7c4 100644 --- a/clustercontroller-standalone/pom.xml +++ b/clustercontroller-standalone/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>clustercontroller-standalone</artifactId> <version>6-SNAPSHOT</version> diff --git a/clustercontroller-utils/pom.xml b/clustercontroller-utils/pom.xml index f151a623ff9..287a85089ca 100644 --- a/clustercontroller-utils/pom.xml +++ b/clustercontroller-utils/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>clustercontroller-utils</artifactId> <version>6-SNAPSHOT</version> diff --git a/component/pom.xml b/component/pom.xml index 92f852e4ba0..18bfd000a84 100755 --- a/component/pom.xml +++ b/component/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>component</artifactId> <packaging>container-plugin</packaging> diff --git a/config-application-package/pom.xml b/config-application-package/pom.xml index 90d4a2d9af6..629c02de1e8 100644 --- a/config-application-package/pom.xml +++ b/config-application-package/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-application-package</artifactId> <packaging>container-plugin</packaging> diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java index 97322fc1c55..330692abb1e 100644 --- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java +++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java @@ -15,6 +15,7 @@ import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.path.Path; import com.yahoo.io.HexDump; import com.yahoo.io.IOUtils; @@ -649,7 +650,7 @@ public class FilesApplicationPackage implements ApplicationPackage { return searchDefinitionContents(); } - private void preprocessXML(File destination, File inputXml, Zone zone) throws ParserConfigurationException, TransformerException, SAXException, IOException { + private void preprocessXML(File destination, File inputXml, ZoneId zone) throws ParserConfigurationException, TransformerException, SAXException, IOException { Document document = new XmlPreProcessor(appDir, inputXml, zone.environment(), zone.region()).run(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); try (FileOutputStream outputStream = new FileOutputStream(destination)) { @@ -658,7 +659,7 @@ public class FilesApplicationPackage implements ApplicationPackage { } @Override - public ApplicationPackage preprocess(Zone zone, RuleConfigDeriver ignored, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException { + public ApplicationPackage preprocess(ZoneId zone, RuleConfigDeriver ignored, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException { IOUtils.recursiveDeleteDir(preprocessedDir); IOUtils.copyDirectory(appDir, preprocessedDir, -1, (dir, name) -> ! name.equals(".preprocessed") && ! name.equals(SERVICES) && diff --git a/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java b/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java index c12a9ad0d06..2dfaf440084 100644 --- a/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java +++ b/config-application-package/src/test/java/com/yahoo/config/model/application/provider/FilesApplicationPackageTest.java @@ -7,6 +7,7 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.io.IOUtils; import org.junit.Rule; import org.junit.Test; @@ -41,13 +42,9 @@ public class FilesApplicationPackageTest { assertTrue(new File(appDir, "hosts.xml").exists()); FilesApplicationPackage app = FilesApplicationPackage.fromFile(appDir); - ApplicationPackage processed = app.preprocess(new Zone(Environment.dev, RegionName.defaultName()), - new RuleConfigDeriver() { - @Override - public void derive(String ruleBaseDir, String outputDir) throws Exception { - } - }, - new BaseDeployLogger()); + ApplicationPackage processed = app.preprocess(ZoneId.from(Environment.dev, RegionName.defaultName()), + (ruleBaseDir, outputDir) -> {}, + new BaseDeployLogger()); assertTrue(new File(appDir, ".preprocessed").exists()); String expectedServices = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><services xmlns:deploy=\"vespa\" xmlns:preprocess=\"properties\" version=\"1.0\">\n" + " <admin version=\"2.0\">\n" + diff --git a/config-bundle/pom.xml b/config-bundle/pom.xml index 7121756ce68..07262141801 100644 --- a/config-bundle/pom.xml +++ b/config-bundle/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-bundle</artifactId> <packaging>container-plugin</packaging> diff --git a/config-class-plugin/pom.xml b/config-class-plugin/pom.xml index 5b3149a13da..71a20782d85 100644 --- a/config-class-plugin/pom.xml +++ b/config-class-plugin/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-class-plugin</artifactId> <packaging>maven-plugin</packaging> diff --git a/config-lib/pom.xml b/config-lib/pom.xml index 2ed993dd052..31648958fa6 100644 --- a/config-lib/pom.xml +++ b/config-lib/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-lib</artifactId> <packaging>container-plugin</packaging> diff --git a/config-model-api/pom.xml b/config-model-api/pom.xml index 6618612df31..743164afb16 100644 --- a/config-model-api/pom.xml +++ b/config-model-api/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-model-api</artifactId> <version>6-SNAPSHOT</version> diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java index c1a786194a2..480d4d05451 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java @@ -4,6 +4,7 @@ package com.yahoo.config.application.api; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.path.Path; import com.yahoo.io.IOUtils; import com.yahoo.io.reader.NamedReader; @@ -255,13 +256,13 @@ public interface ApplicationPackage { * application package. This is the entry point for the multi environment application package support. This method * will not mutate the existing application package. * - * @param zone A valid {@link Zone} instance, used to decide which parts of services to keep and remove + * @param zone A valid {@link ZoneId} instance, used to decide which parts of services to keep and remove * @param ruleConfigDeriver ignored * @param logger A {@link DeployLogger} to add output that will be returned to the user * * @return A new application package instance pointing to a new location */ - default ApplicationPackage preprocess(Zone zone, RuleConfigDeriver ruleConfigDeriver, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException { + default ApplicationPackage preprocess(ZoneId zone, RuleConfigDeriver ruleConfigDeriver, DeployLogger logger) throws IOException, TransformerException, ParserConfigurationException, SAXException { throw new UnsupportedOperationException("This application package does not support preprocessing"); } diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java b/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java index 990bce539ba..3f1c0046a85 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/FileDistribution.java @@ -11,12 +11,21 @@ import java.util.Set; /** * Interface for models towards filedistribution. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public interface FileDistribution { void sendDeployedFiles(String hostName, Set<FileReference> fileReferences); + + /** + * Notifies client which file references to download. Used to start downloading early (while + * preparing application package). + * + * @param hostName host which should be notified about file references to download + * @param fileReferences set of file references to start downloading + */ + void startDownload(String hostName, Set<FileReference> fileReferences); + void reloadDeployFileDistributor(); void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames); 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 521e72ae580..a69835626ea 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 @@ -11,7 +11,6 @@ import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Zone; import java.io.File; -import java.net.URI; import java.util.List; import java.util.Optional; import java.util.Set; @@ -47,14 +46,7 @@ public interface ModelContext { boolean hostedVespa(); Zone zone(); Set<Rotation> rotations(); - - /* - * DEPRECATED - * TODO: Remove when 6.172 and earlier are no longer in use - */ - default URI loadBalancerAddress() { - return URI.create("http://localhost"); - } + default boolean disableFileDistributor() { return false; } } } diff --git a/config-model-fat/pom.xml b/config-model-fat/pom.xml index a4bbba0e6e8..0526c2f6581 100644 --- a/config-model-fat/pom.xml +++ b/config-model-fat/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-model-fat</artifactId> <packaging>bundle</packaging> diff --git a/config-model/pom.xml b/config-model/pom.xml index 0fdc09e1a61..729d1a2ef9b 100644 --- a/config-model/pom.xml +++ b/config-model/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-model</artifactId> <packaging>container-plugin</packaging> diff --git a/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java b/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java index 8b3d983d6c1..78a8c161c3b 100644 --- a/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java +++ b/config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java @@ -5,17 +5,14 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; -import org.w3c.dom.Element; -import java.util.Optional; import java.util.stream.Stream; /** * This class contains a context that is passed to a model builder, and can be used to retrieve the application package, * logger etc. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public final class ConfigModelContext { diff --git a/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java b/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java index 32cb1840e7c..5eb4afcc241 100644 --- a/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java +++ b/config-model/src/main/java/com/yahoo/config/model/admin/AdminModel.java @@ -75,7 +75,11 @@ public class AdminModel extends ConfigModel { public void doBuild(AdminModel model, Element adminElement, ConfigModelContext modelContext) { AbstractConfigProducer parent = modelContext.getParentProducer(); DeployProperties properties = modelContext.getDeployState().getProperties(); - DomAdminV2Builder domBuilder = new DomAdminV2Builder(modelContext.getApplicationType(), modelContext.getDeployState().getFileRegistry(), properties.multitenant(), properties.configServerSpecs()); + DomAdminV2Builder domBuilder = new DomAdminV2Builder(modelContext.getApplicationType(), + modelContext.getDeployState().getFileRegistry(), + properties.multitenant(), + properties.configServerSpecs(), + modelContext.getDeployState().disableFiledistributor()); model.admin = domBuilder.build(parent, adminElement); // TODO: Is required since other models depend on admin. if (parent instanceof ApplicationConfigProducerRoot) { @@ -101,7 +105,11 @@ public class AdminModel extends ConfigModel { public void doBuild(AdminModel model, Element adminElement, ConfigModelContext modelContext) { AbstractConfigProducer parent = modelContext.getParentProducer(); DeployProperties properties = modelContext.getDeployState().getProperties(); - DomAdminV4Builder domBuilder = new DomAdminV4Builder(modelContext, properties.multitenant(), properties.configServerSpecs(), model.getContainerModels()); + DomAdminV4Builder domBuilder = new DomAdminV4Builder(modelContext, + properties.multitenant(), + properties.configServerSpecs(), + model.getContainerModels(), + modelContext.getDeployState().disableFiledistributor()); model.admin = domBuilder.build(parent, adminElement); // TODO: Is required since other models depend on admin. if (parent instanceof ApplicationConfigProducerRoot) { diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java index 25d45dee234..43d3fafdb78 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java @@ -68,8 +68,8 @@ public class DeployState implements ConfigDefinitionStore { private final ValidationOverrides validationOverrides; private final Version wantedNodeVespaVersion; private final Instant now; - private final HostProvisioner provisioner; + private final boolean disableFiledistributor; public static DeployState createTestState() { return new Builder().build(); @@ -82,8 +82,8 @@ public class DeployState implements ConfigDefinitionStore { private DeployState(ApplicationPackage applicationPackage, SearchDocumentModel searchDocumentModel, RankProfileRegistry rankProfileRegistry, FileRegistry fileRegistry, DeployLogger deployLogger, Optional<HostProvisioner> hostProvisioner, DeployProperties properties, Optional<ApplicationPackage> permanentApplicationPackage, Optional<ConfigDefinitionRepo> configDefinitionRepo, - java.util.Optional<Model> previousModel, Set<Rotation> rotations, Zone zone, QueryProfiles queryProfiles, - SemanticRules semanticRules, Instant now, Version wantedNodeVespaVersion) { + java.util.Optional<Model> previousModel, Set<Rotation> rotations, Zone zone, QueryProfiles queryProfiles, + SemanticRules semanticRules, Instant now, Version wantedNodeVespaVersion, boolean disableFiledistributor) { this.logger = deployLogger; this.fileRegistry = fileRegistry; this.rankProfileRegistry = rankProfileRegistry; @@ -102,6 +102,7 @@ public class DeployState implements ConfigDefinitionStore { this.validationOverrides = applicationPackage.getValidationOverrides().map(ValidationOverrides::fromXml).orElse(ValidationOverrides.empty); this.wantedNodeVespaVersion = wantedNodeVespaVersion; this.now = now; + this.disableFiledistributor = disableFiledistributor; } public static HostProvisioner getDefaultModelHostProvisioner(ApplicationPackage applicationPackage) { @@ -169,6 +170,7 @@ public class DeployState implements ConfigDefinitionStore { public ApplicationPackage getApplicationPackage() { return applicationPackage; } + public List<SearchDefinition> getSearchDefinitions() { return searchDefinitions; } @@ -214,6 +216,8 @@ public class DeployState implements ConfigDefinitionStore { public Instant now() { return now; } + public boolean disableFiledistributor() { return disableFiledistributor; } + public static class Builder { private ApplicationPackage applicationPackage = MockApplicationPackage.createEmpty(); @@ -228,6 +232,7 @@ public class DeployState implements ConfigDefinitionStore { private Zone zone = Zone.defaultZone(); private Instant now = Instant.now(); private Version wantedNodeVespaVersion = Vtag.currentVersion; + private boolean disableFiledistributor = false; public Builder applicationPackage(ApplicationPackage applicationPackage) { this.applicationPackage = applicationPackage; @@ -289,13 +294,19 @@ public class DeployState implements ConfigDefinitionStore { return this; } + public Builder disableFiledistributor(boolean disableFiledistributor) { + this.disableFiledistributor = disableFiledistributor; + return this; + } + public DeployState build() { RankProfileRegistry rankProfileRegistry = new RankProfileRegistry(); QueryProfiles queryProfiles = new QueryProfilesBuilder().build(applicationPackage); SemanticRules semanticRules = new SemanticRuleBuilder().build(applicationPackage); SearchDocumentModel searchDocumentModel = createSearchDocumentModel(rankProfileRegistry, logger, queryProfiles); return new DeployState(applicationPackage, searchDocumentModel, rankProfileRegistry, fileRegistry, logger, hostProvisioner, - properties, permanentApplicationPackage, configDefinitionRepo, previousModel, rotations, zone, queryProfiles, semanticRules, now, wantedNodeVespaVersion); + properties, permanentApplicationPackage, configDefinitionRepo, previousModel, rotations, + zone, queryProfiles, semanticRules, now, wantedNodeVespaVersion, disableFiledistributor); } private SearchDocumentModel createSearchDocumentModel(RankProfileRegistry rankProfileRegistry, DeployLogger logger, QueryProfiles queryProfiles) { diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java index 277e02e43e2..2a3ff1ca905 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java @@ -11,6 +11,7 @@ import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.builder.xml.XmlHelper; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; +import com.yahoo.config.provision.Zone; import com.yahoo.text.XML; import com.yahoo.vespa.model.ConfigProducer; import com.yahoo.vespa.model.HostSystem; @@ -147,7 +148,8 @@ public class MockRoot extends AbstractConfigProducerRoot { try { Document doc = XmlHelper.getDocumentBuilder().parse(new InputSource(new StringReader(servicesXml))); - setAdmin(new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, deployState.getFileRegistry(), false, new ArrayList<>()). + setAdmin(new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, deployState.getFileRegistry(), + false, new ArrayList<>(), deployState.disableFiledistributor()). build(this, XML.getChildren(doc.getDocumentElement(), "admin").get(0))); } catch (SAXException | IOException e) { throw new RuntimeException(e); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java index a70e77a17a2..c8918f39834 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/ImmutableImportedSDField.java @@ -79,7 +79,12 @@ public class ImmutableImportedSDField implements ImmutableSDField { @Override public Index getIndex(String name) { - throw createUnsupportedException(); + if (!importedField.fieldName().equals(name)) { + throw new IllegalArgumentException("Getting an index (" + name + ") with different name than the imported field (" + + importedField.fieldName() + ") is not supported"); + } + String targetIndexName = importedField.targetField().getName(); + return importedField.targetField().getIndex(targetIndexName); } @Override diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java new file mode 100644 index 00000000000..eaa85815736 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummayValidator.java @@ -0,0 +1,63 @@ +package com.yahoo.searchdefinition.processing; + +import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.document.DataType; +import com.yahoo.searchdefinition.RankProfileRegistry; +import com.yahoo.searchdefinition.Search; +import com.yahoo.searchdefinition.document.ImportedField; +import com.yahoo.vespa.documentmodel.DocumentSummary; +import com.yahoo.vespa.documentmodel.SummaryField; +import com.yahoo.vespa.model.container.search.QueryProfiles; + +import java.util.Map; + +/** + * Validates that imported fields in document summaries are of supported types. + * Currently, predicate fields are NOT supported. + * + * @author geirst + */ +public class ImportedFieldsInSummayValidator extends Processor { + + + public ImportedFieldsInSummayValidator(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) { + super(search, deployLogger, rankProfileRegistry, queryProfiles); + } + + @Override + public void process() { + if (search.importedFields().isPresent()) { + validateDocumentSummaries(search.getSummaries()); + } + } + + private void validateDocumentSummaries(Map<String, DocumentSummary> summaries) { + for (DocumentSummary summary : summaries.values()) { + for (SummaryField field : summary.getSummaryFields()) { + ImportedField importedField = getImportedField(field); + if (importedField != null) { + validateImportedSummaryField(summary, field, importedField); + } + } + } + } + + private ImportedField getImportedField(SummaryField field) { + return search.importedFields().get().fields().get(field.getName()); + } + + private void validateImportedSummaryField(DocumentSummary summary, SummaryField field, ImportedField importedField) { + if (field.getDataType().equals(DataType.PREDICATE) && + importedField.targetField().getDataType().equals(DataType.PREDICATE)) { + fail(summary, field, "Is of type predicate. Not supported in document summaries"); + } + } + + private void fail(DocumentSummary summary, SummaryField importedField, String msg) { + throw new IllegalArgumentException("For search '" + search.getName() + "', document summary '" + summary.getName() + + "', imported summary field '" + importedField.getName() + "': " + msg); + } +} + + + diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java index 26f98026d4f..90183848094 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java @@ -73,6 +73,7 @@ public class Processing { TensorFieldProcessor::new, RankProfileTypeSettingsProcessor::new, ReferenceFieldsProcessor::new, + ImportedFieldsInSummayValidator::new, FastAccessValidator::new, ReservedMacroNames::new, diff --git a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java index dfa2d2cefb0..3b75be5167d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java @@ -510,22 +510,6 @@ public abstract class AbstractService extends AbstractConfigProducer<AbstractCon } /** - * Sets up this service to be included when generating monitoring config. - * The ymon service name used will be {@link #getServiceType()} - */ - public void monitorService() { - monitorService(getServiceType()); - } - - /** - * Sets up this service to be included when generating ymon config. - * @param ymonServiceName the ymon service name to be used - */ - public void monitorService(String ymonServiceName) { - setProp("ymonService", ymonServiceName); - } - - /** * * The service HTTP port for health status * @return portnumber diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java index d751e332ff3..c540a5f62d2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProxy.java @@ -16,6 +16,8 @@ package com.yahoo.vespa.model; */ public class ConfigProxy extends AbstractService { + public final static int BASEPORT = 19090; + /** * Creates a new ConfigProxy instance. * @@ -31,7 +33,7 @@ public class ConfigProxy extends AbstractService { /** * Returns the desired base port for this service. */ - public int getWantedPort() { return 19090; } + public int getWantedPort() { return BASEPORT; } /** * The desired base port is the only allowed base port. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java index 74512e70ebe..7a1fab8dbd0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java @@ -138,7 +138,8 @@ public class VespaModelFactory implements ModelFactory { .rotations(modelContext.properties().rotations()) .zone(zone) .now(clock.instant()) - .wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion()); + .wantedNodeVespaVersion(modelContext.wantedNodeVespaVersion()) + .disableFiledistributor(modelContext.properties().disableFileDistributor()); modelContext.previousModel().ifPresent(builder::previousModel); return builder.build(); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java index 60d00ce5487..59b7388f5bb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java @@ -1,20 +1,26 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.admin; -import com.yahoo.cloud.config.log.LogdConfig; +import com.yahoo.cloud.config.SlobroksConfig; import com.yahoo.cloud.config.ZookeepersConfig; +import com.yahoo.cloud.config.log.LogdConfig; import com.yahoo.config.model.api.ConfigServerSpec; import com.yahoo.config.model.deploy.DeployProperties; import com.yahoo.config.model.producer.AbstractConfigProducer; -import com.yahoo.cloud.config.SlobroksConfig; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.model.*; +import com.yahoo.vespa.model.AbstractService; +import com.yahoo.vespa.model.ConfigProxy; +import com.yahoo.vespa.model.ConfigSentinel; +import com.yahoo.vespa.model.HostResource; +import com.yahoo.vespa.model.Logd; import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer; import com.yahoo.vespa.model.admin.monitoring.Monitoring; import com.yahoo.vespa.model.admin.monitoring.builder.Metrics; import com.yahoo.vespa.model.container.ContainerCluster; +import com.yahoo.vespa.model.filedistribution.DummyFileDistributionConfigProducer; import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProducer; +import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProvider; import com.yahoo.vespa.model.filedistribution.FileDistributor; import com.yahoo.vespa.model.filedistribution.FileDistributorService; @@ -24,8 +30,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import com.yahoo.vespa.model.HostResource; - /** * This is the admin pseudo-plugin of the Vespa model, responsible for * creating all admin services. @@ -59,12 +63,14 @@ public class Admin extends AbstractConfigProducer implements Serializable { Monitoring monitoring, Metrics metrics, Map<String, MetricsConsumer> legacyMetricsConsumers, - boolean multitenant) { + boolean multitenant, + FileDistributionConfigProducer fileDistributionConfigProducer) { super(parent, "admin"); this.monitoring = monitoring; this.metrics = metrics; this.legacyMetricsConsumers = legacyMetricsConsumers; this.multitenant = multitenant; + this.fileDistribution = fileDistributionConfigProducer; } public Configserver getConfigserver() { @@ -148,10 +154,6 @@ public class Admin extends AbstractConfigProducer implements Serializable { zooKeepersConfigProvider.getConfig(builder); } - public void setFileDistribution(FileDistributionConfigProducer fileDistribution) { - this.fileDistribution = fileDistribution; - } - public FileDistributionConfigProducer getFileDistributionConfigProducer() { return fileDistribution; } @@ -215,11 +217,25 @@ public class Admin extends AbstractConfigProducer implements Serializable { fileDistributor.fileSourceHost() + "'. Hostsystem=" + getHostSystem()); } - FileDistributorService fds = new FileDistributorService(fileDistribution, host.getHost().getHostname(), - fileDistribution.getFileDistributor(), fileDistribution.getOptions(), host == deployHost); - fds.setHostResource(host); - fds.initService(); - fileDistribution.addFileDistributionService(host.getHost(), fds); + FileDistributionConfigProvider configProvider = + new FileDistributionConfigProvider(fileDistributor, + fileDistribution.getOptions(), + host == deployHost, + host.getHost()); + if (fileDistribution.getOptions().disableFiledistributor()) { + DummyFileDistributionConfigProducer dummyFileDistributionConfigProducer = + new DummyFileDistributionConfigProducer(fileDistribution, + host.getHost().getHostname(), + configProvider); + fileDistribution.addFileDistributionConfigProducer(host.getHost(), dummyFileDistributionConfigProducer); + } else { + FileDistributorService fds = new FileDistributorService(fileDistribution, + host.getHost().getHostname(), + configProvider); + fds.setHostResource(host); + fds.initService(); + fileDistribution.addFileDistributionConfigProducer(host.getHost(), fds); + } } private boolean deployHostIsMissing(HostResource deployHost) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java index 942d563d850..e8f1f59310b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Configserver.java @@ -35,7 +35,6 @@ public class Configserver extends AbstractService { portsMeta.on(1).tag("http").tag("config").tag("state"); setProp("clustertype", "admin"); setProp("clustername", "admin"); - monitorService(); } /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java index 0160978773d..8b39a1ecb94 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/FileDistributionOptions.java @@ -21,7 +21,7 @@ public class FileDistributionOptions implements FiledistributorConfig.Producer { private BinaryScaledAmount uploadBitRate = new BinaryScaledAmount(); private BinaryScaledAmount downloadBitRate = new BinaryScaledAmount(); - private boolean disabled = false; + private boolean disableFiledistributor = false; public void downloadBitRate(BinaryScaledAmount amount) { @@ -34,12 +34,12 @@ public class FileDistributionOptions implements FiledistributorConfig.Producer { uploadBitRate = amount; } - public void disabled(boolean value) { - disabled = value; + public void disableFiledistributor(boolean value) { + disableFiledistributor = value; } - public boolean disabled() { - return disabled; + public boolean disableFiledistributor() { + return disableFiledistributor; } private void ensureNonNegative(BinaryScaledAmount amount) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java index 28cf18802cf..8b98dc9d06a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Logserver.java @@ -23,7 +23,6 @@ public class Logserver extends AbstractService { portsMeta.on(3).tag("logtp").tag("telnet").tag("replicator"); setProp("clustertype", "admin"); setProp("clustername", "admin"); - monitorService(); } /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java index cefc08981a4..093694f41b3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java @@ -23,7 +23,6 @@ public class Slobrok extends AbstractService { setProp("index", index); setProp("clustertype", "slobrok"); setProp("clustername", "admin"); - monitorService(); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java index cafdf83608f..3db09f6b566 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/SystemMetrics.java @@ -11,6 +11,7 @@ import java.util.Set; @SuppressWarnings("UnusedDeclaration") // Used by model amenders public class SystemMetrics { public static final String CPU_UTIL = "cpu.util"; + public static final String CPU_SYS_UTIL = "cpu.sys.util"; public static final String DISK_LIMIT = "disk.limit"; public static final String DISK_USED = "disk.used"; public static final String DISK_UTIL = "disk.util"; @@ -23,6 +24,7 @@ public class SystemMetrics { private static MetricSet createSystemMetricSet() { Set<Metric> dockerNodeMetrics = ImmutableSet.of(new Metric(CPU_UTIL), + new Metric(CPU_SYS_UTIL), new Metric(DISK_LIMIT), new Metric(DISK_USED), new Metric(DISK_UTIL), diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java index 2dcf874a66e..2243dc1e682 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java @@ -52,6 +52,7 @@ public class VespaMetricSet { private static Set<Metric> getOtherMetrics() { Set<Metric> metrics = new LinkedHashSet<>(); metrics.add(new Metric("slobrok.heartbeats.failed.count", "slobrok.heartbeats.failed")); + metrics.add(new Metric("logd.processed.lines.count", "logd.processed.lines")); return metrics; } @@ -220,7 +221,9 @@ public class VespaMetricSet { // resource usage metrics.add(new Metric("content.proton.resource_usage.disk.average")); + metrics.add(new Metric("content.proton.resource_usage.disk_utilization.average")); metrics.add(new Metric("content.proton.resource_usage.memory.average")); + metrics.add(new Metric("content.proton.resource_usage.memory_utilization.average")); metrics.add(new Metric("content.proton.resource_usage.memory_mappings.max")); metrics.add(new Metric("content.proton.resource_usage.open_file_descriptors.max")); metrics.add(new Metric("content.proton.documentdb.attribute.resource_usage.enum_store.average")); @@ -230,6 +233,7 @@ public class VespaMetricSet { // transaction log metrics.add(new Metric("content.proton.transactionlog.entries.average")); metrics.add(new Metric("content.proton.transactionlog.disk_usage.average")); + metrics.add(new Metric("content.proton.transactionlog.replay_time.last")); // document store metrics.add(new Metric("content.proton.documentdb.ready.document_store.disk_usage.average")); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java index a3a1a0fbebc..24e56472688 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java @@ -37,16 +37,19 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu private final ApplicationType applicationType; private final List<ConfigServerSpec> configServerSpecs; private final FileRegistry fileRegistry; + private final boolean disableFiledistributor; protected final boolean multitenant; - public DomAdminBuilderBase(ApplicationType applicationType, FileRegistry fileRegistry, boolean multitenant, List<ConfigServerSpec> configServerSpecs) { + DomAdminBuilderBase(ApplicationType applicationType, FileRegistry fileRegistry, boolean multitenant, + List<ConfigServerSpec> configServerSpecs, boolean disableFiledistributor) { this.applicationType = applicationType; this.fileRegistry = fileRegistry; this.multitenant = multitenant; this.configServerSpecs = configServerSpecs; + this.disableFiledistributor = disableFiledistributor; } - protected List<Configserver> getConfigServersFromSpec(AbstractConfigProducer parent) { + List<Configserver> getConfigServersFromSpec(AbstractConfigProducer parent) { List<Configserver> configservers = new ArrayList<>(); for (ConfigServerSpec spec : configServerSpecs) { HostSystem hostSystem = parent.getHostSystem(); @@ -64,23 +67,26 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu @Override protected Admin doBuild(AbstractConfigProducer parent, Element adminElement) { Monitoring monitoring = getMonitoring(getChildWithFallback(adminElement, "monitoring", "yamas")); - Metrics metrics = new MetricsBuilder(applicationType, predefinedMetricSets) .buildMetrics(XML.getChild(adminElement, "metrics")); Map<String, MetricsConsumer> legacyMetricsConsumers = DomMetricBuilderHelper .buildMetricsConsumers(XML.getChild(adminElement, "metric-consumers")); + FileDistributionConfigProducer fileDistributionConfigProducer = getFileDistributionConfigProducer(parent, adminElement); - Admin admin = new Admin(parent, monitoring, metrics, legacyMetricsConsumers, multitenant); - + Admin admin = new Admin(parent, monitoring, metrics, legacyMetricsConsumers, multitenant, fileDistributionConfigProducer); doBuildAdmin(admin, adminElement); - new ModelConfigProvider(admin); - FileDistributionOptions fileDistributionOptions = new DomFileDistributionOptionsBuilder().build(XML.getChild(adminElement, "filedistribution")); - admin.setFileDistribution(new FileDistributionConfigProducer.Builder(fileDistributionOptions).build(parent, fileRegistry)); return admin; } - + + private FileDistributionConfigProducer getFileDistributionConfigProducer(AbstractConfigProducer parent, Element adminElement) { + FileDistributionOptions fileDistributionOptions = FileDistributionOptions.defaultOptions(); + fileDistributionOptions.disableFiledistributor(disableFiledistributor); + fileDistributionOptions = new DomFileDistributionOptionsBuilder(fileDistributionOptions).build(XML.getChild(adminElement, "filedistribution")); + return new FileDistributionConfigProducer.Builder(fileDistributionOptions).build(parent, fileRegistry); + } + private Element getChildWithFallback(Element parent, String childName, String alternativeChildName) { Element child = XML.getChild(parent, childName); if (child != null) return child; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java index dd1d4e36255..d966f3b49f6 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.builder.xml.dom; +import com.yahoo.config.application.api.FileRegistry; import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.model.api.ConfigServerSpec; import com.yahoo.config.model.producer.AbstractConfigProducer; @@ -18,7 +19,6 @@ import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder.DomConfigProducerBu import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; -import com.yahoo.config.application.api.FileRegistry; import org.w3c.dom.Element; import java.util.List; @@ -37,8 +37,9 @@ public class DomAdminV2Builder extends DomAdminBuilderBase { public DomAdminV2Builder(ConfigModelContext.ApplicationType applicationType, FileRegistry fileRegistry, boolean multitenant, - List<ConfigServerSpec> configServerSpecs) { - super(applicationType, fileRegistry, multitenant, configServerSpecs); + List<ConfigServerSpec> configServerSpecs, + boolean disableFiledistributor) { + super(applicationType, fileRegistry, multitenant, configServerSpecs, disableFiledistributor); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java index f33c86134cb..1f7c1ba676c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java @@ -31,8 +31,10 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { private final Collection<ContainerModel> containerModels; private final ConfigModelContext context; - public DomAdminV4Builder(ConfigModelContext context, boolean multitenant, List<ConfigServerSpec> configServerSpecs, Collection<ContainerModel> containerModels) { - super(context.getApplicationType(), context.getDeployState().getFileRegistry(), multitenant, configServerSpecs); + public DomAdminV4Builder(ConfigModelContext context, boolean multitenant, List<ConfigServerSpec> configServerSpecs, + Collection<ContainerModel> containerModels, boolean disableFiledistributor) { + super(context.getApplicationType(), context.getDeployState().getFileRegistry(), multitenant, + configServerSpecs, disableFiledistributor); this.containerModels = containerModels; this.context = context; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java index 8a5d6846a64..9cbdd00899b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomFileDistributionOptionsBuilder.java @@ -15,6 +15,11 @@ import java.util.Optional; * @author hmusum */ public class DomFileDistributionOptionsBuilder { + private final FileDistributionOptions fileDistributionOptions; + + public DomFileDistributionOptionsBuilder(FileDistributionOptions fileDistributionOptions) { + this.fileDistributionOptions = fileDistributionOptions; + } private static void throwExceptionForElementInFileDistribution(String subElement, String reason) { throw new RuntimeException("In element '" + subElement + "' contained in 'filedistribution': " + reason); @@ -34,15 +39,15 @@ public class DomFileDistributionOptionsBuilder { } public FileDistributionOptions build(Element fileDistributionElement) { - FileDistributionOptions options = FileDistributionOptions.defaultOptions(); if (fileDistributionElement != null) { - getAmount("uploadbitrate", fileDistributionElement).ifPresent(options::uploadBitRate); - getAmount("downloadbitrate", fileDistributionElement).ifPresent(options::downloadBitRate); + getAmount("uploadbitrate", fileDistributionElement).ifPresent(fileDistributionOptions::uploadBitRate); + getAmount("downloadbitrate", fileDistributionElement).ifPresent(fileDistributionOptions::downloadBitRate); Element disable = XML.getChild(fileDistributionElement, "disabled"); + if (disable == null) disable = XML.getChild(fileDistributionElement, "disableFiledistributor"); if (disable != null) { - options.disabled(Boolean.valueOf(XML.getValue(disable))); + fileDistributionOptions.disableFiledistributor(Boolean.valueOf(XML.getValue(disable))); } } - return options; + return fileDistributionOptions; } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java index 4969132ccfb..f4287cf982e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/VespaSpoolerService.java @@ -19,7 +19,6 @@ public class VespaSpoolerService extends AbstractService implements SpoolerConfi public VespaSpoolerService(AbstractConfigProducer parent, int index, VespaSpooler spooler) { super(parent, "spooler." + index); this.spooler = spooler; - monitorService("spooler"); } public int getPortCount() { 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 8991bfa6215..5925ec978bb 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 @@ -165,7 +165,6 @@ public class Container extends AbstractService implements } tagServers(); - monitorService(); } private void tagServers() { @@ -331,7 +330,7 @@ public class Container extends AbstractService implements FileDistributionConfigProducer fileDistribution = getRoot().getFileDistributionConfigProducer(); if (fileDistribution != null) { - builder.configid(fileDistribution.getFileDistributorService(getHost()).getConfigId()); + builder.configid(fileDistribution.getConfigProducer(getHost()).getConfigId()); } return builder; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java index 62828b314d0..bce78017bdd 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java @@ -144,6 +144,9 @@ public class ConfigserverCluster extends AbstractConfigProducer if (options.loadBalancerAddress().isPresent()) { builder.loadBalancerAddress(options.loadBalancerAddress().get()); } + if (options.disableFiledistributor().isPresent()) { + builder.disableFiledistributor(options.disableFiledistributor().get()); + } } private String[] getConfigModelPluginDirs() { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java index aeb86ae9d59..866bae6666a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.model.container.configserver.option; import java.util.Optional; /** - * @author tonytv + * @author Tony Vaagenes */ public interface CloudConfigOptions { @@ -44,4 +44,5 @@ public interface CloudConfigOptions { Optional<String> dockerRegistry(); Optional<String> dockerVespaBaseImage(); Optional<String> loadBalancerAddress(); + Optional<Boolean> disableFiledistributor(); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java index 22c42056b3f..33f5edded3c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java @@ -5,6 +5,7 @@ import com.yahoo.component.ComponentId; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator; +import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.text.XML; import com.yahoo.vespa.model.container.component.SimpleComponent; @@ -24,10 +25,14 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig private final Element legacyConfig; public ConnectorFactory(String name, int listenPort) { - this(name, listenPort, null, null); + this(name, listenPort, null, null, null); } - public ConnectorFactory(String name, int listenPort, Element legacyConfig, Element sslKeystoreConfigurator) { + public ConnectorFactory(String name, + int listenPort, + Element legacyConfig, + Element sslKeystoreConfigurator, + Element sslTruststoreConfigurator) { super(new ComponentModel( new BundleInstantiationSpecification(new ComponentId(name), fromString("com.yahoo.jdisc.http.server.jetty.ConnectorFactory"), @@ -35,9 +40,8 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig this.name = name; this.listenPort = listenPort; this.legacyConfig = legacyConfig; - SimpleComponent sslKeyStoreConfigurator = getSslKeyStoreConfigurator(name, sslKeystoreConfigurator); - addChild(sslKeyStoreConfigurator); - inject(sslKeyStoreConfigurator); + addSslKeyStoreConfigurator(name, sslKeystoreConfigurator); + addSslTrustStoreConfigurator(name, sslTruststoreConfigurator); } @Override @@ -149,16 +153,30 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig } } - private static SimpleComponent getSslKeyStoreConfigurator(String name, Element sslKeystoreConfigurator) { - String idSpec = "ssl-keystore-configurator@" + name; - if (sslKeystoreConfigurator != null) { - String className = sslKeystoreConfigurator.getAttribute("class"); - String bundleName = sslKeystoreConfigurator.getAttribute("bundle"); - return new SimpleComponent(new ComponentModel(idSpec, className, bundleName)); + private void addSslKeyStoreConfigurator(String name, Element sslKeystoreConfigurator) { + addSslConfigurator("ssl-keystore-configurator@" + name, + DefaultSslKeyStoreConfigurator.class, + sslKeystoreConfigurator); + } + + private void addSslTrustStoreConfigurator(String name, Element sslKeystoreConfigurator) { + addSslConfigurator("ssl-truststore-configurator@" + name, + DefaultSslTrustStoreConfigurator.class, + sslKeystoreConfigurator); + } + + private void addSslConfigurator(String idSpec, Class<?> defaultImplementation, Element configuratorElement) { + SimpleComponent configuratorComponent; + if (configuratorElement != null) { + String className = configuratorElement.getAttribute("class"); + String bundleName = configuratorElement.getAttribute("bundle"); + configuratorComponent = new SimpleComponent(new ComponentModel(idSpec, className, bundleName)); } else { - return new SimpleComponent( - new ComponentModel(idSpec, DefaultSslKeyStoreConfigurator.class.getName(), "jdisc_http_service")); + configuratorComponent = + new SimpleComponent(new ComponentModel(idSpec, defaultImplementation.getName(), "jdisc_http_service")); } + addChild(configuratorComponent); + inject(configuratorComponent); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java index f2012a609a7..f88c091cd37 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java @@ -12,8 +12,7 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> - * @since 5.21.0 + * @author Einar M R Rosenvinge */ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuilder<ConnectorFactory> { private static final Logger log = Logger.getLogger(JettyConnectorBuilder.class.getName()); @@ -34,7 +33,8 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil } } Element sslKeystoreConfigurator = XML.getChild(serverSpec, "ssl-keystore-configurator"); - return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator); + Element sslTruststoreConfigurator = XML.getChild(serverSpec, "ssl-truststore-configurator"); + return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator, sslTruststoreConfigurator); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java index 7ef54360f00..4257e47218e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java @@ -50,8 +50,6 @@ public abstract class ContentNode extends AbstractService portsMeta.on(0).tag("messaging"); portsMeta.on(1).tag("rpc").tag("status"); portsMeta.on(2).tag("http").tag("status").tag("state"); - - monitorService(); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java index 81aca977400..0405f96cd89 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java @@ -1,8 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; -import com.yahoo.documentmodel.NewDocumentType; -import com.yahoo.vespa.config.content.core.BucketspacesConfig; import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig; import com.yahoo.vespa.config.content.core.StorServerConfig; import com.yahoo.document.select.DocumentSelector; @@ -16,15 +14,13 @@ import org.w3c.dom.Element; import java.util.logging.Logger; - /** * Generates distributor-specific configuration. */ public class DistributorCluster extends AbstractConfigProducer<Distributor> implements StorDistributormanagerConfig.Producer, StorServerConfig.Producer, - MetricsmanagerConfig.Producer, - BucketspacesConfig.Producer { + MetricsmanagerConfig.Producer { public static final Logger log = Logger.getLogger(DistributorCluster.class.getPackage().toString()); @@ -151,20 +147,6 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl builder.is_distributor(true); } - private static final String DEFAULT_BUCKET_SPACE = "default"; - private static final String GLOBAL_BUCKET_SPACE = "global"; - - @Override - public void getConfig(BucketspacesConfig.Builder builder) { - for (NewDocumentType docType : parent.getDocumentDefinitions().values()) { - BucketspacesConfig.Documenttype.Builder docTypeBuilder = new BucketspacesConfig.Documenttype.Builder(); - docTypeBuilder.name(docType.getName()); - String bucketSpace = (parent.isGloballyDistributed(docType) ? GLOBAL_BUCKET_SPACE : DEFAULT_BUCKET_SPACE); - docTypeBuilder.bucketspace(bucketSpace); - builder.documenttype(docTypeBuilder); - } - } - public String getClusterName() { return parent.getName(); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java index fc267133c9f..db849d0ae50 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java @@ -11,6 +11,7 @@ import com.yahoo.config.provision.Zone; import com.yahoo.vespa.config.content.MessagetyperouteselectorpolicyConfig; import com.yahoo.vespa.config.content.FleetcontrollerConfig; import com.yahoo.vespa.config.content.StorDistributionConfig; +import com.yahoo.vespa.config.content.core.BucketspacesConfig; import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig; import com.yahoo.documentmodel.NewDocumentType; import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol; @@ -57,7 +58,8 @@ public class ContentCluster extends AbstractConfigProducer implements StorDistri StorDistributormanagerConfig.Producer, FleetcontrollerConfig.Producer, MetricsmanagerConfig.Producer, - MessagetyperouteselectorpolicyConfig.Producer { + MessagetyperouteselectorpolicyConfig.Producer, + BucketspacesConfig.Producer { // TODO: Make private private String documentSelection; @@ -698,4 +700,18 @@ public class ContentCluster extends AbstractConfigProducer implements StorDistri } } + + private static final String DEFAULT_BUCKET_SPACE = "default"; + private static final String GLOBAL_BUCKET_SPACE = "global"; + + @Override + public void getConfig(BucketspacesConfig.Builder builder) { + for (NewDocumentType docType : getDocumentDefinitions().values()) { + BucketspacesConfig.Documenttype.Builder docTypeBuilder = new BucketspacesConfig.Documenttype.Builder(); + docTypeBuilder.name(docType.getName()); + String bucketSpace = (isGloballyDistributed(docType) ? GLOBAL_BUCKET_SPACE : DEFAULT_BUCKET_SPACE); + docTypeBuilder.bucketspace(bucketSpace); + builder.documenttype(docTypeBuilder); + } + } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java new file mode 100644 index 00000000000..1b1e66594d1 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/DummyFileDistributionConfigProducer.java @@ -0,0 +1,42 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.filedistribution; + +import com.yahoo.cloud.config.filedistribution.FiledistributorConfig; +import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig; +import com.yahoo.cloud.config.filedistribution.FilereferencesConfig; +import com.yahoo.config.model.producer.AbstractConfigProducer; + +/** + * @author hmusum + * <p> + * Dummy file distribution config producer, needed for serving file distribution config when there is no FiledistributorService. + */ +public class DummyFileDistributionConfigProducer extends AbstractConfigProducer implements + FiledistributorConfig.Producer, + FiledistributorrpcConfig.Producer, + FilereferencesConfig.Producer { + + private final FileDistributionConfigProvider configProvider; + + public DummyFileDistributionConfigProducer(AbstractConfigProducer parent, + String hostname, + FileDistributionConfigProvider configProvider) { + super(parent, hostname); + this.configProvider = configProvider; + } + + @Override + public void getConfig(FiledistributorConfig.Builder builder) { + configProvider.getConfig(builder); + } + + @Override + public void getConfig(FiledistributorrpcConfig.Builder builder) { + configProvider.getConfig(builder); + } + + @Override + public void getConfig(FilereferencesConfig.Builder builder) { + configProvider.getConfig(builder); + } +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java index 9e5e3074c5e..c863b180b21 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProducer.java @@ -10,11 +10,13 @@ import java.util.IdentityHashMap; import java.util.Map; /** - * @author tonytv + * @author hmusum + * <p> + * File distribution config producer, delegates getting config to {@link DummyFileDistributionConfigProducer} (one per host) */ public class FileDistributionConfigProducer extends AbstractConfigProducer { - private final Map<Host, FileDistributorService> fileDistributorServices = new IdentityHashMap<>(); + private final Map<Host, AbstractConfigProducer> fileDistributionConfigProducers = new IdentityHashMap<>(); private final FileDistributor fileDistributor; private final FileDistributionOptions options; @@ -24,14 +26,6 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer { this.options = options; } - public FileDistributorService getFileDistributorService(Host host) { - FileDistributorService service = fileDistributorServices.get(host); - if (service == null) { - throw new IllegalStateException("No file distribution service for host " + host); - } - return service; - } - public FileDistributor getFileDistributor() { return fileDistributor; } @@ -40,8 +34,8 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer { return options; } - public void addFileDistributionService(Host host, FileDistributorService fds) { - fileDistributorServices.put(host, fds); + public void addFileDistributionConfigProducer(Host host, AbstractConfigProducer fileDistributionConfigProducer) { + fileDistributionConfigProducers.put(host, fileDistributionConfigProducer); } public static class Builder { @@ -58,4 +52,8 @@ public class FileDistributionConfigProducer extends AbstractConfigProducer { } } + public AbstractConfigProducer getConfigProducer(Host host) { + return fileDistributionConfigProducers.get(host); + } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java new file mode 100644 index 00000000000..a78eb1f74f9 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributionConfigProvider.java @@ -0,0 +1,59 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.filedistribution; + +import com.yahoo.cloud.config.filedistribution.FiledistributorConfig; +import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig; +import com.yahoo.cloud.config.filedistribution.FilereferencesConfig; +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.FileDistribution; +import com.yahoo.vespa.model.ConfigProxy; +import com.yahoo.vespa.model.Host; +import com.yahoo.vespa.model.admin.FileDistributionOptions; + +import java.util.Collection; + +public class FileDistributionConfigProvider { + + private final FileDistributor fileDistributor; + private final FileDistributionOptions fileDistributionOptions; + private final boolean sendAllFiles; + private final Host host; + + public FileDistributionConfigProvider(FileDistributor fileDistributor, + FileDistributionOptions fileDistributionOptions, + boolean sendAllFiles, + Host host) { + this.fileDistributor = fileDistributor; + this.fileDistributionOptions = fileDistributionOptions; + this.sendAllFiles = sendAllFiles; + this.host = host; + } + + public void getConfig(FiledistributorConfig.Builder builder) { + fileDistributionOptions.getConfig(builder); + builder.torrentport(FileDistributorService.BASEPORT + 1); + builder.stateport(FileDistributorService.BASEPORT + 2); + builder.hostname(host.getHostname()); + builder.filedbpath(FileDistribution.getDefaultFileDBPath().toString()); + } + + public void getConfig(FiledistributorrpcConfig.Builder builder) { + // If disabled config proxy should act as file distributor, so use config proxy port + int port = (fileDistributionOptions.disableFiledistributor()) ? ConfigProxy.BASEPORT : FileDistributorService.BASEPORT; + builder.connectionspec("tcp/" + host.getHostname() + ":" + port); + } + + public void getConfig(FilereferencesConfig.Builder builder) { + for (FileReference reference : getFileReferences()) { + builder.filereferences(reference.value()); + } + } + + private Collection<FileReference> getFileReferences() { + if (sendAllFiles) { + return fileDistributor.allFilesToSend(); + } else { + return fileDistributor.filesToSendToHost(host); + } + } +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java index 443c88355ed..f3aad88a446 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java @@ -9,11 +9,10 @@ import com.yahoo.vespa.model.Host; import java.util.*; import java.util.stream.Collectors; - /** * Responsible for directing distribution of files to hosts. * - * @author tonytv + * @author Tony Vaagenes */ public class FileDistributor { @@ -97,6 +96,7 @@ public class FileDistributor { for (Host host : getTargetHosts()) { if ( ! host.getHostname().equals(fileSourceHost)) { dbHandler.sendDeployedFiles(host.getHostname(), filesToSendToHost(host)); + dbHandler.startDownload(host.getHostname(), filesToSendToHost(host)); } } dbHandler.sendDeployedFiles(fileSourceHost, allFilesToSend()); @@ -108,5 +108,5 @@ public class FileDistributor { public void reloadDeployFileDistributor(FileDistribution dbHandler) { dbHandler.reloadDeployFileDistributor(); } - + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java index dd9e057e2fa..986334125d2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributorService.java @@ -4,60 +4,36 @@ package com.yahoo.vespa.model.filedistribution; import com.yahoo.cloud.config.filedistribution.FiledistributorConfig; import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig; import com.yahoo.cloud.config.filedistribution.FilereferencesConfig; -import com.yahoo.config.FileReference; - -import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.vespa.model.AbstractService; -import com.yahoo.vespa.model.admin.FileDistributionOptions; - -import java.util.Collection; /** - * @author tonytv + * @author Tony Vaagenes + * + * Config is produced by {@link FileDistributionConfigProvider} */ public class FileDistributorService extends AbstractService implements FiledistributorConfig.Producer, FiledistributorrpcConfig.Producer, FilereferencesConfig.Producer { - private final static int BASEPORT = 19092; - private final FileDistributor fileDistributor; - private final FileDistributionOptions fileDistributionOptions; - private final boolean sendAllFiles; + final static int BASEPORT = 19092; - private Collection<FileReference> getFileReferences() { - if (sendAllFiles) { - return fileDistributor.allFilesToSend(); - } else { - return fileDistributor.filesToSendToHost(getHost()); - } - } + private final FileDistributionConfigProvider configProvider; - public FileDistributorService(AbstractConfigProducer parent, - String name, - FileDistributor fileDistributor, - FileDistributionOptions fileDistributionOptions, - boolean sendAllFiles) { - super(parent, name); + public FileDistributorService(AbstractConfigProducer parent, String hostname, FileDistributionConfigProvider configProvider) { + super(parent, hostname); + this.configProvider = configProvider; portsMeta.on(0).tag("rpc"); portsMeta.on(1).tag("torrent"); portsMeta.on(2).tag("http").tag("state"); setProp("clustertype", "filedistribution"); setProp("clustername", "admin"); - - this.fileDistributor = fileDistributor; - this.fileDistributionOptions = fileDistributionOptions; - this.sendAllFiles = sendAllFiles; - monitorService(); } @Override public String getStartupCommand() { - // If disabled config proxy should act as file distributor, so don't start this service - return (fileDistributionOptions.disabled()) - ? null - : "exec $ROOT/sbin/vespa-filedistributor" + " --configid " + getConfigId(); + return "exec $ROOT/sbin/vespa-filedistributor" + " --configid " + getConfigId(); } @Override @@ -70,6 +46,7 @@ public class FileDistributorService extends AbstractService implements return true; } + @Override public int getPortCount() { return 3; } @@ -81,24 +58,16 @@ public class FileDistributorService extends AbstractService implements @Override public void getConfig(FiledistributorConfig.Builder builder) { - fileDistributionOptions.getConfig(builder); - builder.torrentport(getRelativePort(1)); - builder.stateport(getRelativePort(2)); - builder.hostname(getHostName()); - builder.filedbpath(FileDistribution.getDefaultFileDBPath().toString()); + configProvider.getConfig(builder); } @Override public void getConfig(FiledistributorrpcConfig.Builder builder) { - // If disabled config proxy should act as file distributor, so use config proxy port - int port = (fileDistributionOptions.disabled()) ? 19090 : getRelativePort(0); - builder.connectionspec("tcp/" + getHostName() + ":" + port); + configProvider.getConfig(builder); } @Override public void getConfig(FilereferencesConfig.Builder builder) { - for (FileReference reference : getFileReferences()) { - builder.filereferences(reference.value()); - } + configProvider.getConfig(builder); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java b/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java index c7610e784ec..87146447dd1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/Dispatch.java @@ -46,7 +46,6 @@ public class Dispatch extends AbstractService implements SearchInterface, setProp("clustertype", "search") .setProp("clustername", dispatchGroup.getClusterName()) .setProp("index", nodeSpec.groupIndex()); - monitorService(); } public static Dispatch createTld(DispatchGroup dispatchGroup, AbstractConfigProducer parent, int rowId) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java index 05b0b382814..8420a1f4b91 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java @@ -19,6 +19,7 @@ import com.yahoo.vespa.model.admin.monitoring.Monitoring; import com.yahoo.vespa.model.application.validation.RestartConfigs; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; import com.yahoo.vespa.model.content.ContentNode; +import com.yahoo.vespa.model.filedistribution.DummyFileDistributionConfigProducer; import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProducer; import com.yahoo.vespa.model.filedistribution.FileDistributorService; import org.w3c.dom.Element; @@ -112,7 +113,6 @@ public class SearchNode extends AbstractService implements portsMeta.on(UNUSED_3).tag("unused"); portsMeta.on(HEALTH_PORT).tag("http").tag("json").tag("health").tag("state"); // Properties are set in DomSearchBuilder - monitorService(); this.tuning = tuning; } @@ -229,10 +229,12 @@ public class SearchNode extends AbstractService implements public void getConfig(FiledistributorrpcConfig.Builder builder) { FileDistributionConfigProducer fileDistribution = getRoot().getFileDistributionConfigProducer(); if (fileDistribution != null) { - FileDistributorService fds = fileDistribution.getFileDistributorService(getHost()); - if (fds != null) { - fds.getConfig(builder); - } + AbstractConfigProducer configProducer = fileDistribution.getConfigProducer(getHost()); + // TODO: Hack, will be fixed when FileDistributorService is gone + if (configProducer instanceof DummyFileDistributionConfigProducer) + ((DummyFileDistributionConfigProducer) configProducer).getConfig(builder); + else + ((FileDistributorService) configProducer).getConfig(builder); } } diff --git a/config-model/src/main/resources/schema/admin.rnc b/config-model/src/main/resources/schema/admin.rnc index b7ec252c4d7..486f1e367e9 100644 --- a/config-model/src/main/resources/schema/admin.rnc +++ b/config-model/src/main/resources/schema/admin.rnc @@ -81,7 +81,7 @@ LogServer = element logserver { FileDistribution = element filedistribution { element uploadbitrate { xsd:string { pattern = "\d+(\.\d*)?\s*[kmgKMG]?" } }? & element downloadbitrate { xsd:string { pattern = "\d+(\.\d*)?\s*[kmgKMG]?" } }? & - element disabled { xsd:boolean }? # Nov. 2017: Temporary, should not be documented + element disableFiledistributor { xsd:boolean }? # Nov. 2017: Temporary, should not be documented } Metrics = element metrics { diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc index 6a90bef7bb2..95ac198adc4 100644 --- a/config-model/src/main/resources/schema/containercluster.rnc +++ b/config-model/src/main/resources/schema/containercluster.rnc @@ -62,6 +62,7 @@ HttpServer = element server { attribute port { xsd:nonNegativeInteger } & ComponentId & element ssl-keystore-configurator { BundleSpec }? & # FOR INTERNAL USE ONLY - SUBJECT TO CHANGE + element ssl-truststore-configurator { BundleSpec }? & # FOR INTERNAL USE ONLY - SUBJECT TO CHANGE GenericConfig* } diff --git a/config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg b/config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg deleted file mode 100644 index 07538a1dce2..00000000000 --- a/config-model/src/test/cfg/application/invalid_legacy_user_config/configs/qr-searchers.cfg +++ /dev/null @@ -1,5 +0,0 @@ -builtin[StemmingSearcher].enabled false -builtin[NoRankingSearcher].enabled false -tag.bold.open "^_" -tag.bold.close "^_" -tag.separator " ... " diff --git a/config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml b/config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml deleted file mode 100644 index 9a13d936e46..00000000000 --- a/config-model/src/test/cfg/application/invalid_legacy_user_config/services.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> -<services version="1.0"> - - - <admin version="2.0"> - <adminserver hostalias="node1" /> - </admin> - -</services> diff --git a/config-model/src/test/derived/importedfields/attributes.cfg b/config-model/src/test/derived/importedfields/attributes.cfg index 62fe0052bc3..f51bbc012b9 100644 --- a/config-model/src/test/derived/importedfields/attributes.cfg +++ b/config-model/src/test/derived/importedfields/attributes.cfg @@ -138,3 +138,23 @@ attribute[].upperbound 9223372036854775807 attribute[].densepostinglistthreshold 0.4 attribute[].tensortype "" attribute[].imported true +attribute[].name "my_predicate_field" +attribute[].datatype PREDICATE +attribute[].collectiontype SINGLE +attribute[].removeifzero false +attribute[].createifnonexistent false +attribute[].fastsearch false +attribute[].huge false +attribute[].sortascending true +attribute[].sortfunction UCA +attribute[].sortstrength PRIMARY +attribute[].sortlocale "" +attribute[].enablebitvectors false +attribute[].enableonlybitvector false +attribute[].fastaccess false +attribute[].arity 8 +attribute[].lowerbound 5 +attribute[].upperbound 300 +attribute[].densepostinglistthreshold 0.4 +attribute[].tensortype "" +attribute[].imported true diff --git a/config-model/src/test/derived/importedfields/child.sd b/config-model/src/test/derived/importedfields/child.sd index 703451c1293..07fa7d15719 100644 --- a/config-model/src/test/derived/importedfields/child.sd +++ b/config-model/src/test/derived/importedfields/child.sd @@ -9,6 +9,7 @@ search child { import field b_ref.string_field as my_string_field {} import field a_ref.int_array_field as my_int_array_field {} import field a_ref.int_wset_field as my_int_wset_field {} + import field a_ref.predicate_field as my_predicate_field {} fieldset myfieldset { fields: my_int_field, my_string_field diff --git a/config-model/src/test/derived/importedfields/imported-fields.cfg b/config-model/src/test/derived/importedfields/imported-fields.cfg index 3f2a083bdc2..59413ca5eb7 100644 --- a/config-model/src/test/derived/importedfields/imported-fields.cfg +++ b/config-model/src/test/derived/importedfields/imported-fields.cfg @@ -18,3 +18,8 @@ attribute[].referencefield "a_ref" attribute[].targetfield "int_wset_field" attribute[].datatype NONE attribute[].collectiontype SINGLE +attribute[].name "my_predicate_field" +attribute[].referencefield "a_ref" +attribute[].targetfield "predicate_field" +attribute[].datatype NONE +attribute[].collectiontype SINGLE diff --git a/config-model/src/test/derived/importedfields/index-info.cfg b/config-model/src/test/derived/importedfields/index-info.cfg index 7cb53c480ce..adba0036409 100644 --- a/config-model/src/test/derived/importedfields/index-info.cfg +++ b/config-model/src/test/derived/importedfields/index-info.cfg @@ -49,6 +49,12 @@ indexinfo[].command[].indexname "my_int_wset_field" indexinfo[].command[].command "multivalue" indexinfo[].command[].indexname "my_int_wset_field" indexinfo[].command[].command "attribute" +indexinfo[].command[].indexname "my_predicate_field" +indexinfo[].command[].command "predicate-bounds [5..300]" +indexinfo[].command[].indexname "my_predicate_field" +indexinfo[].command[].command "index" +indexinfo[].command[].indexname "my_predicate_field" +indexinfo[].command[].command "attribute" indexinfo[].command[].indexname "myfieldset" indexinfo[].command[].command "attribute" indexinfo[].command[].indexname "myfieldset" diff --git a/config-model/src/test/derived/importedfields/parent_a.sd b/config-model/src/test/derived/importedfields/parent_a.sd index eea12375daf..edc81df5609 100644 --- a/config-model/src/test/derived/importedfields/parent_a.sd +++ b/config-model/src/test/derived/importedfields/parent_a.sd @@ -10,5 +10,13 @@ search parent_a { field int_wset_field type weightedset<int> { indexing: attribute } + field predicate_field type predicate { + indexing: attribute + index { + arity: 8 + lower-bound: 5 + upper-bound: 300 + } + } } } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java new file mode 100644 index 00000000000..4e1f8f1edd7 --- /dev/null +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsInSummaryValidatorTestCase.java @@ -0,0 +1,39 @@ +package com.yahoo.searchdefinition.processing; + +import com.yahoo.document.DataType; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * @author geirst + */ +public class ImportedFieldsInSummaryValidatorTestCase { + + @Rule + public final ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void validator_fails_if_imported_predicate_field_is_used_in_document_summary() { + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage("For search 'child', document summary 'my_summary', " + + "imported summary field 'my_predicate_field': Is of type predicate. Not supported in document summaries"); + new SearchModel() + .addImportedField("my_predicate_field", "ref", "predicate_field") + .addSummaryField("my_predicate_field", DataType.PREDICATE) + .resolve(); + } + + private static class SearchModel extends ImportedFieldsResolverTestCase.SearchModel { + + public SearchModel() { + super(); + } + + public void resolve() { + super.resolve(); + new ImportedFieldsInSummayValidator(childSearch, null, null, null).process(); + } + } +} + diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java index 5d270bc085a..9f7c7458738 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java @@ -18,6 +18,8 @@ import com.yahoo.searchdefinition.document.SDField; import com.yahoo.searchdefinition.document.TemporaryImportedField; import com.yahoo.searchdefinition.document.TemporarySDField; import com.yahoo.tensor.TensorType; +import com.yahoo.vespa.documentmodel.DocumentSummary; +import com.yahoo.vespa.documentmodel.SummaryField; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -107,7 +109,7 @@ public class ImportedFieldsResolverTestCase { .resolve(); } - private static class SearchModel { + static class SearchModel { private final ApplicationPackage app = MockApplicationPackage.createEmpty(); public final Search grandParentSearch; @@ -124,6 +126,7 @@ public class ImportedFieldsResolverTestCase { parentSearch.getDocument().addField(createField("attribute_and_index", DataType.INT, "{ attribute | index }")); parentSearch.getDocument().addField(new TemporarySDField("not_attribute", DataType.INT)); parentSearch.getDocument().addField(createField("tensor_field", new TensorDataType(TensorType.fromSpec("tensor(x[])")), "{ attribute }")); + parentSearch.getDocument().addField(createField("predicate_field", DataType.PREDICATE, "{ attribute }")); addRefField(parentSearch, grandParentSearch, "ref"); addImportedField(parentSearch, "ancient_field", "ref", "ancient_field"); @@ -163,6 +166,16 @@ public class ImportedFieldsResolverTestCase { return this; } + public SearchModel addSummaryField(String fieldName, DataType dataType) { + DocumentSummary summary = childSearch.getSummary("my_summary"); + if (summary == null) { + summary = new DocumentSummary("my_summary"); + childSearch.addSummary(summary); + } + summary.add(new SummaryField(fieldName, dataType)); + return this; + } + public void resolve() { resolve(grandParentSearch); resolve(parentSearch); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java index 946624a1cdb..fed9000f72b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/AdminTestCase.java @@ -8,7 +8,6 @@ import com.yahoo.cloud.config.SentinelConfig; import com.yahoo.config.model.ApplicationConfigProducerRoot; import com.yahoo.config.model.deploy.DeployProperties; import com.yahoo.config.model.deploy.DeployState; -import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.model.test.TestDriver; import com.yahoo.config.model.test.TestRoot; import com.yahoo.config.provision.ApplicationId; @@ -24,11 +23,9 @@ import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.StatisticsComponent; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; -import org.junit.Ignore; import org.junit.Test; import java.util.Set; -import java.util.stream.IntStream; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; @@ -288,7 +285,7 @@ public class AdminTestCase { " <admin version='2.0'>" + " <adminserver hostalias='node0' />" + " <filedistribution>" + - " <disabled>true</disabled>" + + " <disableFiledistributor>true</disableFiledistributor>" + " </filedistribution>" + " </admin>" + "</services>"; @@ -307,4 +304,24 @@ public class AdminTestCase { assertThat(sentinelConfig.service(2).name(), is("logd")); // No filedistributor service } + + @Test + public void testDisableFileDistributorForAllApps() { + DeployState state = new DeployState.Builder() + .disableFiledistributor(true) + .properties( + new DeployProperties.Builder(). + zone(new Zone(Environment.dev, RegionName.from("baz"))). + applicationId(new ApplicationId.Builder(). + tenant("quux"). + applicationName("foo").instanceName("bim").build()).build()).build(); + TestRoot root = new TestDriver().buildModel(state); + String localhost = HostName.getLocalhost(); + SentinelConfig sentinelConfig = root.getConfig(SentinelConfig.class, "hosts/" + localhost); + assertThat(sentinelConfig.service().size(), is(3)); + assertThat(sentinelConfig.service(0).name(), is("logserver")); + assertThat(sentinelConfig.service(1).name(), is("slobrok")); + assertThat(sentinelConfig.service(2).name(), is("logd")); + } + } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java index 6c1bf6c9748..446ceb686e8 100755 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2BuilderTest.java @@ -207,7 +207,10 @@ public class DomAdminV2BuilderTest extends DomBuilderTest { } private Admin buildAdmin(Element xml, boolean multitenant, List<ConfigServerSpec> configServerSpecs) { - final DomAdminV2Builder domAdminBuilder = new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, root.getDeployState().getFileRegistry(), multitenant, configServerSpecs); + final DomAdminV2Builder domAdminBuilder = + new DomAdminV2Builder(ConfigModelContext.ApplicationType.DEFAULT, + root.getDeployState().getFileRegistry(), multitenant, + configServerSpecs, root.getDeployState().disableFiledistributor()); Admin admin = domAdminBuilder.build(root, xml); admin.addPerHostServices(root.getHostSystem().getHosts(), new DeployProperties.Builder().build()); return admin; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java deleted file mode 100644 index e7df151a3c4..00000000000 --- a/config-model/src/test/java/com/yahoo/vespa/model/clients/test/Gateway20TestCase.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.clients.test; - -import com.yahoo.container.ComponentsConfig; -import com.yahoo.container.QrConfig; -import com.yahoo.container.QrConfig.Builder; -import com.yahoo.net.HostName; -import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.*; - -/** - * @author Gunnar Gauslaa Bergem - */ -public class Gateway20TestCase { - - private static String hostname = HostName.getLocalhost(); // Using the same way of getting hostname as filedistribution model - - @Test - public void testSimpleDocprocV3() throws Exception { - VespaModel model = new VespaModelCreatorWithFilePkg("src/test/cfg/clients/simpleconfig.v2.docprocv3").create(); - QrConfig qrConfig = new QrConfig((Builder) model.getConfig(new QrConfig.Builder(), "container/container.0")); - assertEquals(qrConfig.rpc().enabled(), true); - assertEquals("filedistribution/" + hostname, qrConfig.filedistributor().configid()); - assertEquals("container.container.0", qrConfig.discriminator()); - - ComponentsConfig componentsConfig = new ComponentsConfig((ComponentsConfig.Builder) model.getConfig(new ComponentsConfig.Builder(), "container/container.0")); - ArrayList<String> components = new ArrayList<>(); - for (ComponentsConfig.Components component : componentsConfig.components()) { - components.add(component.id()); - } - List<String> expectedComponents = Arrays.asList("com.yahoo.docproc.jdisc.DocumentProcessingHandler", - "com.yahoo.feedhandler.VespaFeedHandler", - "com.yahoo.feedhandler.VespaFeedHandlerCompatibility", - "com.yahoo.feedhandler.VespaFeedHandlerGet", - "com.yahoo.feedhandler.VespaFeedHandlerRemove", - "com.yahoo.feedhandler.VespaFeedHandlerRemoveLocation", - "com.yahoo.feedhandler.VespaFeedHandlerStatus", - "com.yahoo.feedhandler.VespaFeedHandlerVisit", - "com.yahoo.search.handler.SearchHandler", - "com.yahoo.container.jdisc.state.StateHandler"); - assertTrue(components.containsAll(expectedComponents)); - } - - @Test - public void testAdvanced() throws Exception { - VespaModel model = new VespaModelCreatorWithFilePkg("src/test/cfg/clients/advancedconfig.v2").create(); - - QrConfig qrConfig = new QrConfig((Builder) model.getConfig(new QrConfig.Builder(), "container/container.0")); - assertEquals(qrConfig.rpc().enabled(), true); - assertEquals(qrConfig.filedistributor().configid(), "filedistribution/" + hostname); - assertEquals("container.container.0", qrConfig.discriminator()); - - qrConfig = new QrConfig((Builder) model.getConfig(new QrConfig.Builder(), "container/container.0")); - assertEquals(qrConfig.rpc().enabled(), true); - assertEquals(qrConfig.filedistributor().configid(), "filedistribution/" + hostname); - } - -} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java b/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java index e2c8f2e2c52..1784fe0e974 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java @@ -6,8 +6,7 @@ import com.yahoo.vespa.model.container.configserver.option.CloudConfigOptions; import java.util.Optional; /** - * @author lulf - * @since 5. + * @author Ulf Lilleengen */ public class TestOptions implements CloudConfigOptions { private Optional<Integer> rpcPort = Optional.empty(); @@ -20,6 +19,7 @@ public class TestOptions implements CloudConfigOptions { private Optional<Boolean> useVespaVersionInRequest = Optional.empty(); private Optional<Boolean> hostedVespa = Optional.empty(); private Optional<Integer> numParallelTenantLoaders = Optional.empty(); + private Optional<Boolean> disableFiledistributor = Optional.empty(); @Override public Optional<Integer> rpcPort() { @@ -118,6 +118,9 @@ public class TestOptions implements CloudConfigOptions { @Override public Optional<String> loadBalancerAddress() { return Optional.empty(); } + @Override + public Optional<Boolean> disableFiledistributor() { return disableFiledistributor; } + public TestOptions numParallelTenantLoaders(int numLoaders) { this.numParallelTenantLoaders = Optional.of(numLoaders); return this; 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 d09211aea45..5e093bdb32a 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 @@ -16,6 +16,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Zone; import com.yahoo.container.ComponentsConfig; +import com.yahoo.container.QrConfig; import com.yahoo.container.config.StatisticsRequestHandler; import com.yahoo.container.core.ChainsConfig; import com.yahoo.container.core.VipStatusConfig; @@ -25,6 +26,7 @@ import com.yahoo.container.jdisc.JdiscBindingsConfig; import com.yahoo.container.servlet.ServletConfigConfig; import com.yahoo.container.usability.BindingsOverviewHandler; import com.yahoo.jdisc.http.ServletPathsConfig; +import com.yahoo.net.HostName; import com.yahoo.prelude.cluster.QrMonitorConfig; import com.yahoo.vespa.model.AbstractService; import com.yahoo.vespa.model.VespaModel; @@ -61,7 +63,6 @@ import static org.junit.Assert.fail; /** * @author gjoranv - * @since 5.1.9 */ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { @@ -571,6 +572,41 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { assertEquals(ContainerModelBuilder.HOSTED_VESPA_STATUS_FILE, vipStatusConfig.statusfile()); } + @Test + public void qrconfig_is_produced() throws IOException, SAXException { + String servicesXml = + "<services>" + + "<admin version='3.0'>" + + " <nodes count='1'/>" + + "</admin>" + + "<jdisc id ='default' version='1.0'>" + + " <nodes>" + + " <node hostalias='node1' />" + + " </nodes>" + + "</jdisc>" + + "</services>"; + + ApplicationPackage applicationPackage = new MockApplicationPackage.Builder() + .withServices(servicesXml) + .build(); + VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() + .applicationPackage(applicationPackage) + .properties(new DeployProperties.Builder().build()) + .build()); + + String hostname = HostName.getLocalhost(); // Using the same way of getting hostname as filedistribution model + + 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()); + assertEquals(true, config.rpc().enabled()); + assertEquals("", config.rpc().host()); + assertEquals(false, config.restartOnDeploy()); + assertEquals(false, config.coveragereports()); + assertEquals("filedistribution/" + hostname, config.filedistributor().configid()); + } + private Element generateContainerElementWithRenderer(String rendererId) { return DomBuilderTest.parse( "<jdisc id='default' version='1.0'>", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java index 1e24b055095..54c4aabf44c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java @@ -7,6 +7,7 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.jdisc.FilterBindingsProvider; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator; +import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.SimpleComponent; import com.yahoo.vespa.model.container.http.ConnectorFactory; @@ -16,7 +17,9 @@ import org.w3c.dom.Element; import org.xml.sax.SAXException; import java.io.IOException; +import java.util.Arrays; import java.util.List; +import java.util.Set; import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType; import static org.hamcrest.CoreMatchers.equalTo; @@ -28,7 +31,6 @@ import static org.junit.Assert.assertThat; /** * @author einarmr - * @since 5.15 */ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBase { @@ -190,12 +192,13 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas } @Test - public void ssl_keystore_configurator_can_be_overriden() throws IOException, SAXException { + public void ssl_keystore_and_truststore_configurator_can_be_overriden() throws IOException, SAXException { Element clusterElem = DomBuilderTest.parse( "<jdisc id='default' version='1.0' jetty='true'>", " <http>", " <server port='9000' id='foo'>", " <ssl-keystore-configurator class='com.yahoo.MySslKeyStoreConfigurator' bundle='mybundle'/>", + " <ssl-truststore-configurator class='com.yahoo.MySslTrustStoreConfigurator' bundle='mybundle'/>", " </server>", " <server port='9001' id='bar'/>", " </http>", @@ -204,27 +207,47 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas createModel(root, clusterElem); ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default"); List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class); - { ConnectorFactory firstConnector = connectorFactories.get(0); - assertThat(firstConnector.getInjectedComponentIds(), hasItem("ssl-keystore-configurator@foo")); - assertThat(firstConnector.getInjectedComponentIds().size(), equalTo(1)); - SimpleComponent sslKeystoreConfigurator = firstConnector.getChildrenByTypeRecursive(SimpleComponent.class).get(0); - BundleInstantiationSpecification spec = sslKeystoreConfigurator.model.bundleInstantiationSpec; - assertThat(spec.classId.toString(), is("com.yahoo.MySslKeyStoreConfigurator")); - assertThat(spec.bundle.toString(), is("mybundle")); + assertConnectorHasInjectedComponents(firstConnector, "ssl-keystore-configurator@foo", "ssl-truststore-configurator@foo"); + assertComponentHasClassNameAndBundle(getChildComponent(firstConnector, 0), + "com.yahoo.MySslKeyStoreConfigurator", + "mybundle"); + assertComponentHasClassNameAndBundle(getChildComponent(firstConnector, 1), + "com.yahoo.MySslTrustStoreConfigurator", + "mybundle"); } { - ConnectorFactory secondFactory = connectorFactories.get(1); - assertThat(secondFactory.getInjectedComponentIds(), hasItem("ssl-keystore-configurator@bar")); - assertThat(secondFactory.getInjectedComponentIds().size(), equalTo(1)); - SimpleComponent sslKeystoreConfigurator = secondFactory.getChildrenByTypeRecursive(SimpleComponent.class).get(0); - BundleInstantiationSpecification spec = sslKeystoreConfigurator.model.bundleInstantiationSpec; - assertThat(spec.classId.toString(), is(DefaultSslKeyStoreConfigurator.class.getName())); - assertThat(spec.bundle.toString(), is("jdisc_http_service")); + ConnectorFactory secondConnector = connectorFactories.get(1); + assertConnectorHasInjectedComponents(secondConnector, "ssl-keystore-configurator@bar", "ssl-truststore-configurator@bar"); + assertComponentHasClassNameAndBundle(getChildComponent(secondConnector, 0), + DefaultSslKeyStoreConfigurator.class.getName(), + "jdisc_http_service"); + assertComponentHasClassNameAndBundle(getChildComponent(secondConnector, 1), + DefaultSslTrustStoreConfigurator.class.getName(), + "jdisc_http_service"); } } + private static void assertConnectorHasInjectedComponents(ConnectorFactory connectorFactory, String... componentNames) { + Set<String> injectedComponentIds = connectorFactory.getInjectedComponentIds(); + assertThat(injectedComponentIds.size(), equalTo(componentNames.length)); + Arrays.stream(componentNames) + .forEach(name -> assertThat(injectedComponentIds, hasItem(name))); + } + + private static SimpleComponent getChildComponent(ConnectorFactory connectorFactory, int index) { + return connectorFactory.getChildrenByTypeRecursive(SimpleComponent.class).get(index); + } + + private static void assertComponentHasClassNameAndBundle(SimpleComponent simpleComponent, + String className, + String bundleName) { + BundleInstantiationSpecification spec = simpleComponent.model.bundleInstantiationSpec; + assertThat(spec.classId.toString(), is(className)); + assertThat(spec.bundle.toString(), is(bundleName)); + } + private void assertJettyServerInConfig() { ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default"); List<JettyHttpServer> jettyServers = cluster.getChildrenByTypeRecursive(JettyHttpServer.class); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java index 5f18b28d6ce..41bba055a50 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentSearchClusterTest.java @@ -1,9 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; +import com.yahoo.vespa.config.content.core.BucketspacesConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.content.utils.ContentClusterBuilder; +import com.yahoo.vespa.model.content.utils.DocType; import com.yahoo.vespa.model.content.utils.SearchDefinitionBuilder; import org.junit.Test; @@ -15,6 +17,7 @@ import static com.yahoo.config.model.test.TestUtil.joinLines; import static com.yahoo.vespa.model.content.utils.ContentClusterUtils.createCluster; import static com.yahoo.vespa.model.content.utils.SearchDefinitionBuilder.createSearchDefinitions; import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; /** * Unit tests for content search cluster. @@ -36,8 +39,8 @@ public class ContentSearchClusterTest { private static ContentCluster createClusterWithGlobalType() throws Exception { return createCluster(new ContentClusterBuilder().docTypes(Arrays.asList( - new ContentClusterBuilder.DocType("global", true), - new ContentClusterBuilder.DocType("regular"))).getXml(), + DocType.indexGlobal("global"), + DocType.index("regular"))).getXml(), createSearchDefinitions("global", "regular")); } @@ -108,10 +111,31 @@ public class ContentSearchClusterTest { .content("field ref_to_c type reference<c> { indexing: attribute }").build()); searchDefinitions.add(new SearchDefinitionBuilder().name("c").build()); return createCluster(new ContentClusterBuilder().docTypes(Arrays.asList( - new ContentClusterBuilder.DocType("a"), - new ContentClusterBuilder.DocType("b", true), - new ContentClusterBuilder.DocType("c", true))).getXml(), + DocType.index("a"), + DocType.indexGlobal("b"), + DocType.indexGlobal("c"))).getXml(), searchDefinitions); } + private static BucketspacesConfig getBucketspacesConfig(ContentCluster cluster) { + BucketspacesConfig.Builder builder = new BucketspacesConfig.Builder(); + cluster.getConfig(builder); + return new BucketspacesConfig(builder); + } + + private static void assertDocumentType(String expName, String expBucketSpace, BucketspacesConfig.Documenttype docType) { + assertEquals(expName, docType.name()); + assertEquals(expBucketSpace, docType.bucketspace()); + } + + @Test + public void require_that_bucket_spaces_config_is_produced_for_content_cluster() throws Exception { + BucketspacesConfig config = getBucketspacesConfig(createClusterWithGlobalType()); + assertEquals(2, config.documenttype().size()); + assertDocumentType("global", "global", config.documenttype(0)); + assertDocumentType("regular", "default", config.documenttype(1)); + // Safeguard against flipping the switch + assertFalse(config.enable_multiple_bucket_spaces()); + } + } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java index d4e804d3f95..48b7ccdad6b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java @@ -1,23 +1,22 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; -import com.yahoo.vespa.config.content.core.BucketspacesConfig; import com.yahoo.vespa.config.content.core.StorCommunicationmanagerConfig; import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig; import com.yahoo.vespa.config.content.core.StorServerConfig; import com.yahoo.config.model.test.MockRoot; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.content.utils.ContentClusterUtils; +import com.yahoo.vespa.model.content.utils.DocType; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import org.junit.Test; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.hamcrest.Matchers.*; + /** * Test for content DistributorCluster. */ @@ -303,100 +302,48 @@ public class DistributorTest { return new StorDistributormanagerConfig(builder); } - private static class DocDef { - public final String type; - public final String mode; - public final boolean global; - - private DocDef(String type, String mode, boolean global) { - this.type = type; - this.mode = mode; - this.global = global; - } - - public static DocDef storeOnly(String type) { - return new DocDef(type, "store-only", false); - } - - public static DocDef index(String type) { - return new DocDef(type, "index", false); - } - - public static DocDef indexGlobal(String type) { - return new DocDef(type, "index", true); - } - - public static DocDef streaming(String type) { - return new DocDef(type, "streaming", false); - } - } - - private String generateXmlForDocDefs(DocDef... defs) { + private String generateXmlForDocTypes(DocType... docTypes) { return "<content id='storage'>\n" + - " <documents>\n" + - Arrays.stream(defs) - .map(def -> String.format(" <document type='%s' mode='%s' global='%s'/>", - def.type, def.mode, (def.global ? "true" : "false"))) - .collect(Collectors.joining("\n")) + - "\n </documents>\n" + - "</content>"; + DocType.listToXml(docTypes) + + "\n</content>"; } @Test public void bucket_activation_disabled_if_no_documents_in_indexed_mode() { StorDistributormanagerConfig config = clusterXmlToConfig( - generateXmlForDocDefs(DocDef.storeOnly("music"))); + generateXmlForDocTypes(DocType.storeOnly("music"))); assertThat(config.disable_bucket_activation(), is(true)); } @Test public void bucket_activation_enabled_with_single_indexed_document() { StorDistributormanagerConfig config = clusterXmlToConfig( - generateXmlForDocDefs(DocDef.index("music"))); + generateXmlForDocTypes(DocType.index("music"))); assertThat(config.disable_bucket_activation(), is(false)); } @Test public void bucket_activation_enabled_with_multiple_indexed_documents() { StorDistributormanagerConfig config = clusterXmlToConfig( - generateXmlForDocDefs(DocDef.index("music"), - DocDef.index("movies"))); + generateXmlForDocTypes(DocType.index("music"), + DocType.index("movies"))); assertThat(config.disable_bucket_activation(), is(false)); } @Test public void bucket_activation_enabled_if_at_least_one_document_indexed() { StorDistributormanagerConfig config = clusterXmlToConfig( - generateXmlForDocDefs(DocDef.storeOnly("music"), - DocDef.streaming("bunnies"), - DocDef.index("movies"))); + generateXmlForDocTypes(DocType.storeOnly("music"), + DocType.streaming("bunnies"), + DocType.index("movies"))); assertThat(config.disable_bucket_activation(), is(false)); } @Test public void bucket_activation_disabled_for_single_streaming_type() { StorDistributormanagerConfig config = clusterXmlToConfig( - generateXmlForDocDefs(DocDef.streaming("music"))); + generateXmlForDocTypes(DocType.streaming("music"))); assertThat(config.disable_bucket_activation(), is(true)); } - private BucketspacesConfig clusterXmlToBucketspacesConfig(String xml) { - BucketspacesConfig.Builder builder = new BucketspacesConfig.Builder(); - parse(xml).getConfig(builder); - return new BucketspacesConfig(builder); - } - - private void assertDocumentType(String expName, String expBucketSpace, BucketspacesConfig.Documenttype docType) { - assertEquals(expName, docType.name()); - assertEquals(expBucketSpace, docType.bucketspace()); - } - - @Test - public void bucket_spaces_config_is_produced_for_distributor_cluster() { - BucketspacesConfig config = clusterXmlToBucketspacesConfig( - generateXmlForDocDefs(DocDef.index("music"), DocDef.indexGlobal("movies"))); - assertEquals(2, config.documenttype().size()); - assertDocumentType("movies", "global", config.documenttype(0)); - assertDocumentType("music", "default", config.documenttype(1)); - } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java index 592e90efd22..95c57bb544c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java @@ -18,29 +18,10 @@ import static com.yahoo.config.model.test.TestUtil.joinLines; */ public class ContentClusterBuilder { - public static class DocType { - private final String name; - private final boolean global; - - public DocType(String name, boolean global) { - this.name = name; - this.global = global; - } - - public DocType(String name) { - this(name, false); - } - - public String toXml() { - return (global ? "<document mode='index' type='" + name + "' global='true'/>" : - "<document mode='index' type='" + name + "'/>"); - } - } - private String name = "mycluster"; private int redundancy = 1; private int searchableCopies = 1; - private List<DocType> docTypes = Arrays.asList(new DocType("test")); + private List<DocType> docTypes = Arrays.asList(DocType.index("test")); private String groupXml = getSimpleGroupXml(); private Optional<String> dispatchXml = Optional.empty(); private Optional<Double> protonDiskLimit = Optional.empty(); @@ -66,7 +47,7 @@ public class ContentClusterBuilder { public ContentClusterBuilder docTypes(String ... docTypes) { this.docTypes = Arrays.asList(docTypes).stream(). - map(type -> new DocType(type)). + map(type -> DocType.index(type)). collect(Collectors.toList()); return this; } @@ -103,9 +84,7 @@ public class ContentClusterBuilder { public String getXml() { String xml = joinLines("<content version='1.0' id='" + name + "'>", " <redundancy>" + redundancy + "</redundancy>", - " <documents>", - docTypes.stream().map(DocType::toXml).collect(Collectors.joining("\n")), - " </documents>", + DocType.listToXml(docTypes), " <engine>", " <proton>", " <searchable-copies>" + searchableCopies + "</searchable-copies>", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java index 3a2633ed7b7..e557e3674b5 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.model.content.utils; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.model.api.HostProvisioner; +import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.provision.InMemoryProvisioner; import com.yahoo.config.model.provision.SingleNodeProvisioner; @@ -11,9 +12,11 @@ import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.model.test.MockRoot; import com.yahoo.text.XML; import com.yahoo.vespa.model.admin.Admin; +import com.yahoo.vespa.model.admin.FileDistributionOptions; import com.yahoo.vespa.model.admin.monitoring.DefaultMonitoring; import com.yahoo.vespa.model.admin.monitoring.builder.Metrics; import com.yahoo.vespa.model.content.cluster.ContentCluster; +import com.yahoo.vespa.model.filedistribution.FileDistributionConfigProducer; import org.w3c.dom.Document; import java.util.Collections; @@ -57,7 +60,8 @@ public class ContentClusterUtils { public static ContentCluster createCluster(String clusterXml, MockRoot root) throws Exception { Document doc = XML.getDocument(clusterXml); - Admin admin = new Admin(root, new DefaultMonitoring("vespa", 60), new Metrics(), Collections.emptyMap(), false); + Admin admin = new Admin(root, new DefaultMonitoring("vespa", 60), new Metrics(), Collections.emptyMap(), false, + new FileDistributionConfigProducer.Builder(FileDistributionOptions.defaultOptions()).build(root, new MockFileRegistry())); ConfigModelContext context = ConfigModelContext.create(null, root.getDeployState(), null, root, null); return new ContentCluster.Builder(admin).build(Collections.emptyList(), context, doc.getDocumentElement()); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java new file mode 100644 index 00000000000..3a5f679509b --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/DocType.java @@ -0,0 +1,54 @@ +package com.yahoo.vespa.model.content.utils; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Definition of a document type used for testing. + * + * @author geirst + */ +public class DocType { + private final String type; + private final String mode; + private final boolean global; + + private DocType(String type, String mode, boolean global) { + this.type = type; + this.mode = mode; + this.global = global; + } + + public String toXml() { + return (global ? "<document mode='" + mode + "' type='" + type + "' global='true'/>" : + "<document mode='" + mode + "' type='" + type + "'/>"); + } + + public static DocType storeOnly(String type) { + return new DocType(type, "store-only", false); + } + + public static DocType index(String type) { + return new DocType(type, "index", false); + } + + public static DocType indexGlobal(String type) { + return new DocType(type, "index", true); + } + + public static DocType streaming(String type) { + return new DocType(type, "streaming", false); + } + + public static String listToXml(DocType... docTypes) { + return listToXml(Arrays.asList(docTypes)); + } + + public static String listToXml(List<DocType> docTypes) { + return "<documents>\n" + + docTypes.stream().map(DocType::toXml).collect(Collectors.joining("\n")) + "\n" + + "</documents>"; + } + +} diff --git a/config-model/src/test/schema-test-files/services.xml b/config-model/src/test/schema-test-files/services.xml index a02346193cc..af316c2e3a7 100644 --- a/config-model/src/test/schema-test-files/services.xml +++ b/config-model/src/test/schema-test-files/services.xml @@ -108,6 +108,7 @@ <server port="4080" id="myServer"> <ssl-keystore-configurator class="com.yahoo.MySslKeyStoreConfigurator" bundle="mybundle" /> + <ssl-truststore-configurator class="com.yahoo.MySslTrustStoreConfigurator" bundle="mybundle" /> </server> <server port="4081" id="anotherServer"> <config name="container.jdisc.config.http-server"> diff --git a/config-provisioning/pom.xml b/config-provisioning/pom.xml index ebe7d460989..05f94973017 100644 --- a/config-provisioning/pom.xml +++ b/config-provisioning/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa</groupId> <artifactId>config-provisioning</artifactId> diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java index 77faa45ebe5..111073aca77 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java @@ -11,8 +11,20 @@ import java.util.Optional; */ public interface Deployer { + + /** + * Creates a new deployment from the active application, if available. Will use the default timeout for deployment. + * + * @param application the active application to be redeployed + * @return a new deployment from the local active, or empty if a local active application + * was not present for this id (meaning it either is not active or active on another + * node in the config server cluster) + */ + Optional<Deployment> deployFromLocalActive(ApplicationId application); + /** - * Creates a new deployment from the active application, if available. + * Creates a new deployment from the active application, if available. Prefer {@link #deployFromLocalActive(ApplicationId)} + * if possible, this method is for testing and will override the default timeout for deployment. * * @param application the active application to be redeployed * @param timeout the timeout to use for each individual deployment operation diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java index b98020c260b..95c80be1fc1 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java @@ -34,6 +34,7 @@ public class RegionName implements Comparable<RegionName> { return region; } + // TODO: Add verification of region name. public static RegionName from(String region) { return new RegionName(region); } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java index 92e38b402e3..26c20d56d63 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java @@ -5,10 +5,11 @@ import com.google.common.base.Strings; import com.google.inject.Inject; import com.yahoo.cloud.config.ConfigserverConfig; +import java.util.Objects; import java.util.Optional; /** - * The zone (environment + region) of this runtime. + * The zone (environment + region) of this runtime, and some other information. * An injected instance of this will return the correct current environment and region. * Components can use this to obtain information about which zone they are running in. * @@ -16,11 +17,10 @@ import java.util.Optional; */ public class Zone { - private final Environment environment; - private final RegionName region; private final SystemName systemName; private final FlavorDefaults flavorDefaults; private final Optional<NodeFlavors> nodeFlavors; + private final ZoneId id; @Inject public Zone(ConfigserverConfig configserverConfig, NodeFlavors nodeFlavors) { @@ -31,19 +31,14 @@ public class Zone { nodeFlavors); } - /** Create from environment and region */ + /** Create from environment and region. Use for testing. */ public Zone(Environment environment, RegionName region) { - this(SystemName.defaultSystem(), environment, region, "default"); + this(SystemName.defaultSystem(), environment, region); } - /** Create from system, environment and region */ + /** Create from system, environment and region. Use for testing. */ public Zone(SystemName systemName, Environment environment, RegionName region) { - this(systemName, environment, region, "default"); - } - - /** Create from environment and region. Useful for testing. */ - public Zone(SystemName system, Environment environment, RegionName region, String defaultFlavor) { - this(system, environment, region, new FlavorDefaults(defaultFlavor), null); + this(systemName, environment, region, new FlavorDefaults("default"), null); } private Zone(SystemName systemName, @@ -51,18 +46,26 @@ public class Zone { RegionName region, FlavorDefaults flavorDefaults, NodeFlavors nodeFlavors) { - this.environment = environment; - this.region = region; + this.id = ZoneId.from(environment, region); this.flavorDefaults = flavorDefaults; this.systemName = systemName; this.nodeFlavors = Optional.ofNullable(nodeFlavors); } + /** Returns the id of this */ + public ZoneId id() { + return id; + } + /** Returns the current environment */ - public Environment environment() { return environment; } + public Environment environment() { + return id.environment(); + } /** Returns the current region */ - public RegionName region() { return region; } + public RegionName region() { + return id.region(); + } /** Returns the current system */ public SystemName system() { return systemName; } @@ -80,21 +83,19 @@ public class Zone { @Override public String toString() { - return "zone " + environment + "." + region; + return id.toString(); } - - @Override - public int hashCode() { return environment().hashCode() + 7 * region.hashCode();} - + @Override public boolean equals(Object o) { - if (o == this) return true; + if (this == o) return true; if ( ! (o instanceof Zone)) return false; - - Zone other = (Zone)o; - if ( this.environment() != other.environment()) return false; - if ( ! this.region.equals(other.region)) return false; - return true; + return Objects.equals(id, ((Zone) o).id); + } + + @Override + public int hashCode() { + return id.hashCode(); } private static class FlavorDefaults { @@ -151,3 +152,4 @@ public class Zone { } } + diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java new file mode 100644 index 00000000000..d51a8d5e0c9 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ZoneId.java @@ -0,0 +1,70 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision; + +import java.util.Objects; + +/** + * Unique identifier for a Zone; use when referencing them. + * + * Serialised form is 'environment.region'. + * + * @author jvenstad + */ +public class ZoneId { + // TODO: Replace usages of zone + region with usages of this. + + private final Environment environment; + private final RegionName region; + + private ZoneId(Environment environment, RegionName region) { + this.environment = Objects.requireNonNull(environment); + this.region = Objects.requireNonNull(region); + } + + public static ZoneId from(Environment environment, RegionName region) { + return new ZoneId(environment, region); + } + + public static ZoneId from(String environment, String region) { + return from(Environment.from(environment), RegionName.from(region)); + } + /** Create from a serialised ZoneId. Inverse of {@code ZoneId.value()}. */ + public static ZoneId from(String value) { + String[] parts = value.split("\\."); + return from(parts[0], parts[1]); + } + + public Environment environment() { + return environment; + } + + public RegionName region() { + return region; + } + + /** Returns the serialised value of this. Inverse of {@code ZoneId.from(String value)}. */ + public String value() { + return environment + "." + region; + } + + @Override + public String toString() { + return "zone " + value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if ( ! (o instanceof ZoneId)) return false; + ZoneId id = (ZoneId) o; + return environment == id.environment && + Objects.equals(region, id.region); + } + + @Override + public int hashCode() { + return Objects.hash(environment, region); + } + +} + diff --git a/config-proxy/pom.xml b/config-proxy/pom.xml index 0aadb1bbb12..a266f68efe2 100644 --- a/config-proxy/pom.xml +++ b/config-proxy/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config-proxy</artifactId> <packaging>jar</packaging> diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java index dd1f0d36abd..0ed5d04e36e 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ClientUpdater.java @@ -21,9 +21,7 @@ class ClientUpdater { private final RpcServer rpcServer; private final DelayedResponses delayedResponses; - ClientUpdater(RpcServer rpcServer, - ConfigProxyStatistics statistics, - DelayedResponses delayedResponses) { + ClientUpdater(RpcServer rpcServer, ConfigProxyStatistics statistics, DelayedResponses delayedResponses) { this.rpcServer = rpcServer; this.statistics = statistics; this.delayedResponses = delayedResponses; @@ -37,57 +35,39 @@ class ClientUpdater { * @param config new config */ void updateSubscribers(RawConfig config) { - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Config updated for " + config.getKey() + "," + config.getGeneration()); - } + log.log(LogLevel.DEBUG, () -> "Config updated for " + config.getKey() + "," + config.getGeneration()); sendResponse(config); } private void sendResponse(RawConfig config) { if (config.isError()) { statistics.incErrorCount(); } - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Sending response for " + config.getKey() + "," + config.getGeneration()); - } + log.log(LogLevel.DEBUG, () -> "Sending response for " + config.getKey() + "," + config.getGeneration()); DelayQueue<DelayedResponse> responseDelayQueue = delayedResponses.responses(); - if (log.isLoggable(LogLevel.SPAM)) { - log.log(LogLevel.SPAM, "Delayed response queue: " + responseDelayQueue); - } + log.log(LogLevel.SPAM, () -> "Delayed response queue: " + responseDelayQueue); if (responseDelayQueue.size() == 0) { - log.log(LogLevel.DEBUG, "There exists no matching element on delayed response queue for " + config.getKey()); + log.log(LogLevel.DEBUG, () -> "There exists no matching element on delayed response queue for " + config.getKey()); return; } else { - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Delayed response queue has " + responseDelayQueue.size() + " elements"); - } + log.log(LogLevel.DEBUG, () -> "Delayed response queue has " + responseDelayQueue.size() + " elements"); } - DelayedResponse[] responses = new DelayedResponse[1]; - responses = responseDelayQueue.toArray(responses); + DelayedResponse[] responses = responseDelayQueue.toArray(new DelayedResponse[0]); boolean found = false; - if (responses.length > 0) { - for (DelayedResponse response : responses) { - JRTServerConfigRequest request = response.getRequest(); - if (request.getConfigKey().equals(config.getKey())) { - if (!delayedResponses.remove(response)) { - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Could not remove " + config.getKey() + " from delayed delayedResponses queue, already removed"); - } - continue; - } + for (DelayedResponse response : responses) { + JRTServerConfigRequest request = response.getRequest(); + if (request.getConfigKey().equals(config.getKey())) { + if (delayedResponses.remove(response)) { found = true; - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Call returnOkResponse for " + config.getKey() + "," + config.getGeneration()); - } + log.log(LogLevel.DEBUG, () -> "Call returnOkResponse for " + config.getKey() + "," + config.getGeneration()); rpcServer.returnOkResponse(request, config); + } else { + log.log(LogLevel.INFO, "Could not remove " + config.getKey() + " from delayedResponses queue, already removed"); } } - } if (!found) { - log.log(LogLevel.DEBUG, "Found no recipient for " + config.getKey() + " in delayed response queue"); - } - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Finished updating config for " + config.getKey() + "," + config.getGeneration()); + log.log(LogLevel.DEBUG, () -> "Found no recipient for " + config.getKey() + " in delayed response queue"); } + log.log(LogLevel.DEBUG, () -> "Finished updating config for " + config.getKey() + "," + config.getGeneration()); } } diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java index fe5976e9c1f..68985bd598a 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java @@ -20,7 +20,6 @@ import java.util.logging.Logger; * * @author hmusum */ -// TODO: Rename now that it also support file distribution request public class ConfigProxyRpcServer implements Runnable, TargetWatcher, RpcServer { private final static Logger log = Logger.getLogger(ConfigProxyRpcServer.class.getName()); diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java index 7fd173ab98d..7914b7a80b6 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCache.java @@ -19,7 +19,6 @@ import java.util.logging.Logger; /** * @author hmusum - * @since 5.1.9 */ public class MemoryCache { private static final Logger log = Logger.getLogger(MemoryCache.class.getName()); @@ -41,9 +40,7 @@ public class MemoryCache { public void put(RawConfig config) { if (config.isError()) return; - if (log.isLoggable(LogLevel.DEBUG)) { - log.log(LogLevel.DEBUG, "Putting '" + config + "' into memory cache"); - } + log.log(LogLevel.DEBUG, () -> "Putting '" + config + "' into memory cache"); cache.put(new ConfigCacheKey(config.getKey(), config.getDefMd5()), config); } diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java index a1c07b91155..4b6e9172f8b 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/MemoryCacheConfigClient.java @@ -10,8 +10,9 @@ import java.util.List; import java.util.logging.Logger; /** + * The client used for getting config when running in 'memorycache' mode. + * * @author hmusum - * @since 5.1.10 */ class MemoryCacheConfigClient implements ConfigSourceClient { @@ -30,11 +31,11 @@ class MemoryCacheConfigClient implements ConfigSourceClient { */ @Override public RawConfig getConfig(RawConfig input, JRTServerConfigRequest request) { - log.log(LogLevel.DEBUG, "Getting config from cache"); + log.log(LogLevel.DEBUG, () -> "Getting config from cache"); ConfigKey<?> key = input.getKey(); RawConfig cached = cache.get(new ConfigCacheKey(key, input.getDefMd5())); if (cached != null) { - log.log(LogLevel.DEBUG, "Found config " + key + " in cache"); + log.log(LogLevel.DEBUG, () -> "Found config " + key + " in cache"); return cached; } else { return null; diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java index d0095121c8e..0f80f228b36 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java @@ -23,7 +23,6 @@ import java.util.logging.Logger; * An Rpc client to a config source * * @author hmusum - * @since 5.1.9 */ class RpcConfigSourceClient implements ConfigSourceClient { @@ -83,7 +82,7 @@ class RpcConfigSourceClient implements ConfigSourceClient { Target target = supervisor.connect(spec); target.invokeSync(req, 30.0); if (target.isValid()) { - log.log(LogLevel.DEBUG, "Created connection to config source at " + spec.toString()); + log.log(LogLevel.DEBUG, () -> "Created connection to config source at " + spec.toString()); return; } else { log.log(LogLevel.INFO, "Could not connect to config source at " + spec.toString()); @@ -123,13 +122,11 @@ class RpcConfigSourceClient implements ConfigSourceClient { RawConfig ret = null; if (cachedConfig != null) { - log.log(LogLevel.DEBUG, "Found config " + configCacheKey + " in cache, generation=" + cachedConfig.getGeneration() + + log.log(LogLevel.DEBUG, () -> "Found config " + configCacheKey + " in cache, generation=" + cachedConfig.getGeneration() + ",configmd5=" + cachedConfig.getConfigMd5()); - if (log.isLoggable(LogLevel.SPAM)) { - log.log(LogLevel.SPAM, "input config=" + input + ",cached config=" + cachedConfig); - } + log.log(LogLevel.SPAM, () -> "input config=" + input + ",cached config=" + cachedConfig); if (ProxyServer.configOrGenerationHasChanged(cachedConfig, request)) { - log.log(LogLevel.SPAM, "Cached config is not equal to requested, will return it"); + log.log(LogLevel.SPAM, () -> "Cached config is not equal to requested, will return it"); if (delayedResponses.remove(delayedResponse)) { // unless another thread already did it ret = cachedConfig; @@ -148,9 +145,9 @@ class RpcConfigSourceClient implements ConfigSourceClient { private void subscribeToConfig(RawConfig input, ConfigCacheKey configCacheKey) { synchronized (activeSubscribersLock) { if (activeSubscribers.containsKey(configCacheKey)) { - log.log(LogLevel.DEBUG, "Already a subscriber running for: " + configCacheKey); + log.log(LogLevel.DEBUG, () -> "Already a subscriber running for: " + configCacheKey); } else { - log.log(LogLevel.DEBUG, "Could not find good config in cache, creating subscriber for: " + configCacheKey); + log.log(LogLevel.DEBUG, () -> "Could not find good config in cache, creating subscriber for: " + configCacheKey); UpstreamConfigSubscriber subscriber = new UpstreamConfigSubscriber(input, clientUpdater, configSourceSet, timingValues, requesterPool, memoryCache); try { subscriber.subscribe(); diff --git a/config/pom.xml b/config/pom.xml index f73c5b7bfd8..cfa3a692181 100755 --- a/config/pom.xml +++ b/config/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>config</artifactId> <packaging>container-plugin</packaging> diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java index 2aa7c66ce87..8acab56d838 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java @@ -130,4 +130,32 @@ public class ConfigSetSubscriptionTest { assertEquals(hS.getConfig().stringVal(), "new StringVal"); } + @Test + public void requireThatWeGetLatestConfigWhenTwoUpdatesBeforeClientChecks() { + ConfigSet myConfigs = new ConfigSet(); + AppConfig.Builder a0builder = new AppConfig.Builder().message("A message, 1"); + myConfigs.addBuilder("app/0", a0builder); + ConfigSubscriber subscriber = new ConfigSubscriber(myConfigs); + ConfigHandle<AppConfig> hA0 = subscriber.subscribe(AppConfig.class, "app/0"); + + assertTrue(subscriber.nextConfig(0)); + assertTrue(hA0.isChanged()); + assertEquals(hA0.getConfig().message(), "A message, 1"); + + assertFalse(subscriber.nextConfig(10)); + assertFalse(hA0.isChanged()); + assertEquals(hA0.getConfig().message(), "A message, 1"); + + //Reconfigure two times in a row + a0builder.message("A new message, 2"); + subscriber.reload(1); + a0builder.message("An even newer message, 3"); + subscriber.reload(2); + + // Should pick up the last one + assertTrue(subscriber.nextConfig(0)); + assertTrue(hA0.isChanged()); + assertEquals(hA0.getConfig().message(), "An even newer message, 3"); + } + } diff --git a/config/src/tests/configagent/configagent.cpp b/config/src/tests/configagent/configagent.cpp index 7eb9442f492..bc90cb458db 100644 --- a/config/src/tests/configagent/configagent.cpp +++ b/config/src/tests/configagent/configagent.cpp @@ -22,17 +22,17 @@ public: bool abort() override { return false; } bool isAborted() const override { return false; } void setError(int errorCode) override { (void) errorCode; } + bool verifyState(const ConfigState &) const override { return false; } const ConfigKey _key; }; class MyConfigResponse : public ConfigResponse { public: - MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool isUpdated, bool valid, - int64_t timestamp, const vespalib::string & md5, const std::string & errorMsg, int errorC0de, bool iserror) + MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool valid, int64_t timestamp, + const vespalib::string & md5, const std::string & errorMsg, int errorC0de, bool iserror) : _key(key), _value(value), - _isUpdated(isUpdated), _fillCalled(false), _valid(valid), _state(md5, timestamp), @@ -54,7 +54,6 @@ public: const ConfigKey _key; const ConfigValue _value; - bool _isUpdated; bool _fillCalled; bool _valid; const ConfigState _state; @@ -64,24 +63,19 @@ public: Trace _trace; -/** - MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool isUpdated, bool valid, - int64_t timestamp, const vespalib::string & md5, int64_t prevTimestamp, const vespalib::string &prevMd5, - const std::string & errorMsg, int errorC0de, bool iserror) -*/ - static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value) + static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value, uint64_t timestamp = 10, const vespalib::string & md5 = "a") { - return ConfigResponse::UP(new MyConfigResponse(key, value, true, true, 10, "a", "", 0, false)); + return ConfigResponse::UP(new MyConfigResponse(key, value, true, timestamp, md5, "", 0, false)); } static ConfigResponse::UP createServerErrorResponse(const ConfigKey & key, const ConfigValue & value) { - return ConfigResponse::UP(new MyConfigResponse(key, value, false, true, 10, "a", "whinewhine", 2, true)); + return ConfigResponse::UP(new MyConfigResponse(key, value, true, 10, "a", "whinewhine", 2, true)); } static ConfigResponse::UP createConfigErrorResponse(const ConfigKey & key, const ConfigValue & value) { - return ConfigResponse::UP(new MyConfigResponse(key, value, false, false, 10, "a", "", 0, false)); + return ConfigResponse::UP(new MyConfigResponse(key, value, false, 10, "a", "", 0, false)); } }; @@ -106,6 +100,9 @@ public: void handle(std::unique_ptr<ConfigUpdate> update) override { + if (_update) { + update->merge(*_update); + } _update = std::move(update); } @@ -154,12 +151,40 @@ TEST("require that successful request is delivered to holder") { handler.handleResponse(MyConfigRequest(testKey), MyConfigResponse::createOKResponse(testKey, testValue)); ASSERT_TRUE(latch->poll()); ConfigUpdate::UP update(latch->provide()); - ASSERT_TRUE(update.get() != NULL); + ASSERT_TRUE(update); ASSERT_TRUE(update->hasChanged()); MyConfig cfg(update->getValue()); ASSERT_EQUAL("l33t", cfg.myField); } +TEST("require that important(the change) request is delivered to holder even if it was not the last") { + const ConfigKey testKey(ConfigKey::create<MyConfig>("mykey")); + const ConfigValue testValue1(createValue("l33t", "a")); + const ConfigValue testValue2(createValue("l34t", "b")); + IConfigHolder::SP latch(new MyHolder()); + + FRTConfigAgent handler(latch, testTimingValues); + handler.handleResponse(MyConfigRequest(testKey), + MyConfigResponse::createOKResponse(testKey, testValue1, 1, testValue1.getMd5())); + ASSERT_TRUE(latch->poll()); + ConfigUpdate::UP update(latch->provide()); + ASSERT_TRUE(update); + ASSERT_TRUE(update->hasChanged()); + MyConfig cfg(update->getValue()); + ASSERT_EQUAL("l33t", cfg.myField); + + handler.handleResponse(MyConfigRequest(testKey), + MyConfigResponse::createOKResponse(testKey, testValue2, 2, testValue2.getMd5())); + handler.handleResponse(MyConfigRequest(testKey), + MyConfigResponse::createOKResponse(testKey, testValue2, 3, testValue2.getMd5())); + ASSERT_TRUE(latch->poll()); + update = latch->provide(); + ASSERT_TRUE(update); + ASSERT_TRUE(update->hasChanged()); + MyConfig cfg2(update->getValue()); + ASSERT_EQUAL("l34t", cfg2.myField); +} + TEST("require that successful request sets correct wait time") { const ConfigKey testKey(ConfigKey::create<MyConfig>("mykey")); const ConfigValue testValue(createValue("l33t", "a")); diff --git a/config/src/tests/configholder/configholder.cpp b/config/src/tests/configholder/configholder.cpp index 83249da3824..2c6fa2016bf 100644 --- a/config/src/tests/configholder/configholder.cpp +++ b/config/src/tests/configholder/configholder.cpp @@ -46,7 +46,7 @@ TEST("Require that polling for elements work") holder.handle(ConfigUpdate::UP(new ConfigUpdate(value, true, 0))); ASSERT_TRUE(holder.poll()); holder.provide(); - ASSERT_TRUE(holder.poll()); + ASSERT_FALSE(holder.poll()); } TEST_MT_F("Require that wait is interrupted", 2, ConfigHolder) diff --git a/config/src/tests/frt/frt.cpp b/config/src/tests/frt/frt.cpp index 1fe91af885b..5f4b7dee215 100644 --- a/config/src/tests/frt/frt.cpp +++ b/config/src/tests/frt/frt.cpp @@ -6,10 +6,6 @@ #include <vespa/config/common/configdefinition.h> #include <vespa/config/frt/connection.h> #include <vespa/config/frt/frtsource.h> -#include <vespa/config/frt/frtconfigresponse.h> -#include <vespa/config/frt/frtconfigrequest.h> -#include <vespa/config/frt/frtconfigrequestv2.h> -#include <vespa/config/frt/frtconfigresponsev2.h> #include <vespa/config/frt/frtconfigrequestv3.h> #include <vespa/config/frt/frtconfigresponsev3.h> #include <vespa/vespalib/data/slime/slime.h> @@ -232,107 +228,27 @@ namespace { TEST_F("require that empty config response does not validate", RPCFixture()) { - FRTConfigResponseV1 fail1(f1.createEmptyRequest()); + FRTConfigResponseV3 fail1(f1.createEmptyRequest()); ASSERT_FALSE(fail1.validateResponse()); ASSERT_FALSE(fail1.hasValidResponse()); ASSERT_TRUE(fail1.isError()); } TEST_F("require that response containing errors does not validate", RPCFixture()) { - FRTConfigResponseV1 fail1(f1.createErrorRequest()); + FRTConfigResponseV3 fail1(f1.createErrorRequest()); ASSERT_FALSE(fail1.validateResponse()); ASSERT_FALSE(fail1.hasValidResponse()); ASSERT_TRUE(fail1.isError()); ASSERT_TRUE(fail1.errorCode() != 0); } -TEST_F("require that valid response validates", RPCFixture()) { - std::vector<vespalib::string> vec; - vec.push_back("bar \"foo\""); - FRTConfigResponseV1 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15, vec, "mn")); - ASSERT_TRUE(ok.validateResponse()); - ASSERT_TRUE(ok.hasValidResponse()); -} - TEST_F("require that response contains all values", RPCFixture()) { - FRTConfigResponseV1 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15)); + FRTConfigResponseV3 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15)); ASSERT_FALSE(ok.validateResponse()); ASSERT_FALSE(ok.hasValidResponse()); } -TEST_F("require that valid response returns values after fill", RPCFixture()) { - std::vector<vespalib::string> vec; - vec.push_back("bar \"foo\""); - FRTConfigResponseV1 ok(f1.createOKResponse("foo", "baz", "bim", "boo", 12, 15, vec, "mn")); - ASSERT_TRUE(ok.validateResponse()); - ASSERT_TRUE(ok.hasValidResponse()); - - // Should not be valid - ASSERT_TRUE(ConfigKey() == ok.getKey()); - ASSERT_TRUE(ConfigValue() == ok.getValue()); - - ok.fill(); - ConfigKey key(ok.getKey()); - ASSERT_EQUAL("foo", key.getDefName()); - ASSERT_EQUAL("baz", key.getDefMd5()); - ASSERT_EQUAL("bim", key.getConfigId()); - ASSERT_EQUAL("mn", key.getDefNamespace()); - - ConfigValue value(ok.getValue()); - ASSERT_TRUE(vec == value.getLines()); -} - -TEST("require that request parameters are correctly initialized") { - ConnectionMock conn; - std::vector<vespalib::string> schema; - schema.push_back("foo"); - schema.push_back("bar"); - ConfigKey key("foo", "bar", "bim", "boo", schema); - FRTConfigRequestV1 frtReq(key, &conn, "mymd5", 1337, 8); - - FRT_RPCRequest * req = frtReq.getRequest(); - FRT_Values & params(*req->GetParams()); - ASSERT_EQUAL("bar", std::string(params[0]._string._str)); - ASSERT_EQUAL("", std::string(params[1]._string._str)); - ASSERT_EQUAL("boo", std::string(params[2]._string._str)); - ASSERT_EQUAL("foo", std::string(params[3]._string._str)); - ASSERT_EQUAL("mymd5", std::string(params[4]._string._str)); - ASSERT_EQUAL(1337u, params[5]._intval64); - ASSERT_EQUAL(8u, params[6]._intval64); - ASSERT_EQUAL("bim", std::string(params[7]._string._str)); - ASSERT_EQUAL(2u, params[8]._string_array._len); - ASSERT_EQUAL("foo", std::string(params[8]._string_array._pt[0]._str)); - ASSERT_EQUAL("bar", std::string(params[8]._string_array._pt[1]._str)); - ASSERT_EQUAL(1u, params[9]._intval32); -} - -TEST("require that request is aborted") { - MyAbortHandler handler; - ConnectionMock conn; - ConfigKey key("foo", "bar", "bim", "boo"); - FRTConfigRequestV1 frtReq(key, &conn, "mymd5", 1337, 8); - frtReq.getRequest()->SetAbortHandler(&handler); - ASSERT_FALSE(frtReq.isAborted()); - ASSERT_TRUE(frtReq.abort()); -} - -TEST_FF("require that request is invoked", SourceFixture(), - FRTFixture(f1)) -{ - f2.result.state = ConfigState("foo", 3); - f2.src.getConfig(); - ASSERT_TRUE(f2.src.getCurrentRequest().verifyKey(f1.key)); - ASSERT_FALSE(f2.src.getCurrentRequest().verifyKey(ConfigKey("foo", "bal", "bim", "boo", std::vector<vespalib::string>()))); - ASSERT_FALSE(f2.src.getCurrentRequest().verifyState(ConfigState("foo", 0))); - ASSERT_FALSE(f2.src.getCurrentRequest().verifyState(ConfigState("foo", 1))); - ASSERT_FALSE(f2.src.getCurrentRequest().verifyState(ConfigState("bar", 1))); - ASSERT_TRUE(f2.src.getCurrentRequest().verifyState(ConfigState("foo", 3))); - ASSERT_TRUE(f2.result.notified); - f2.src.close(); -} - -TEST_FF("require that request is config task is scheduled", SourceFixture(), - FRTFixture(f1)) +TEST_FF("require that request is config task is scheduled", SourceFixture(), FRTFixture(f1)) { f2.src.getConfig(); ASSERT_TRUE(f2.result.notified); @@ -349,96 +265,6 @@ TEST_FF("require that request is config task is scheduled", SourceFixture(), f2.src.close(); } -TEST("require that v2 request is correctly initialized") { - ConnectionMock conn; - ConfigKey key = ConfigKey::create<MyConfig>("foobi"); - vespalib::string md5 = "mymd5"; - int64_t currentGeneration = 3; - int64_t wantedGeneration = 4; - vespalib::string hostName = "myhost"; - int64_t timeout = 3000; - Trace traceIn(3); - traceIn.trace(2, "Hei"); - FRTConfigRequestV2 v2req(&conn, key, md5, currentGeneration, wantedGeneration, hostName, timeout, traceIn); - ConfigDefinition origDef(MyConfig::CONFIG_DEF_SCHEMA); - - FRT_RPCRequest * req = v2req.getRequest(); - ASSERT_TRUE(req != NULL); - FRT_Values & params(*req->GetParams()); - std::string json(params[0]._string._str); - Slime slime; - JsonFormat::decode(Memory(json), slime); - Inspector & root(slime.get()); - EXPECT_EQUAL(2, root[REQUEST_VERSION].asLong()); - EXPECT_EQUAL(key.getDefName(), root[REQUEST_DEF_NAME].asString().make_string()); - EXPECT_EQUAL(key.getDefNamespace(), root[REQUEST_DEF_NAMESPACE].asString().make_string()); - EXPECT_EQUAL(key.getDefMd5(), root[REQUEST_DEF_MD5].asString().make_string()); - 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(wantedGeneration, root[REQUEST_WANTED_GENERATION].asLong()); - EXPECT_EQUAL(md5, root[REQUEST_CONFIG_MD5].asString().make_string()); - EXPECT_EQUAL(timeout, root[REQUEST_TIMEOUT].asLong()); - Trace trace; - trace.deserialize(root[REQUEST_TRACE]); - EXPECT_TRUE(trace.shouldTrace(2)); - EXPECT_TRUE(trace.shouldTrace(3)); - EXPECT_FALSE(trace.shouldTrace(4)); - EXPECT_EQUAL(timeout, root[REQUEST_TIMEOUT].asLong()); - ConfigDefinition def; - def.deserialize(root[REQUEST_DEF_CONTENT]); - EXPECT_EQUAL(origDef.asString(), def.asString()); - ConfigResponse::UP response(v2req.createResponse(req)); - req->GetReturn()->AddString("foobar"); - EXPECT_TRUE(response->validateResponse()); -} - -TEST("require that v2 reponse is correctly initialized") { - ConnectionMock conn; - Slime slime; - ConfigKey key = ConfigKey::create<MyConfig>("foobi"); - vespalib::string md5 = "mymd5"; - int64_t generation = 3; - vespalib::string hostname = "myhhost"; - Trace traceIn(3); - traceIn.trace(2, "Hei!"); - Cursor & root(slime.setObject()); - root.setLong(RESPONSE_VERSION, 2ul); - root.setString(RESPONSE_DEF_NAME, Memory(key.getDefName())); - root.setString(RESPONSE_DEF_NAMESPACE, Memory(key.getDefNamespace())); - 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.setLong(RESPONSE_CONFIG_GENERATION, generation); - traceIn.serialize(root.setObject(RESPONSE_TRACE)); - Cursor & payload(root.setObject(RESPONSE_PAYLOAD)); - payload.setString("myField", "foobiar"); - SimpleBuffer buf; - JsonFormat::encode(slime, buf, true); - FRT_RPCRequest * req = conn.allocRPCRequest(); - req->GetReturn()->AddString(buf.get().make_string().c_str()); - FRTConfigResponseV2 response(req); - ASSERT_TRUE(response.validateResponse()); - response.fill(); - Trace trace(response.getTrace()); - EXPECT_TRUE(trace.shouldTrace(3)); - EXPECT_FALSE(trace.shouldTrace(4)); - ConfigKey responseKey(response.getKey()); - EXPECT_EQUAL(key.getDefName(), responseKey.getDefName()); - EXPECT_EQUAL(key.getDefNamespace(), responseKey.getDefNamespace()); - EXPECT_EQUAL(key.getDefMd5(), responseKey.getDefMd5()); - EXPECT_EQUAL(key.getConfigId(), responseKey.getConfigId()); - EXPECT_EQUAL(hostname, response.getHostName()); - ConfigState state(response.getConfigState()); - EXPECT_EQUAL(md5, state.md5); - EXPECT_EQUAL(generation, state.generation); - ConfigValue value(response.getValue()); - MyConfig::UP config(value.newInstance<MyConfig>()); - EXPECT_EQUAL("foobiar", config->myField); - req->SubRef(); -} - TEST("require that v3 request is correctly initialized") { ConnectionMock conn; ConfigKey key = ConfigKey::create<MyConfig>("foobi"); @@ -449,7 +275,13 @@ TEST("require that v3 request is correctly initialized") { int64_t timeout = 3000; Trace traceIn(3); traceIn.trace(2, "Hei"); - FRTConfigRequestV3 v3req(&conn, key, md5, currentGeneration, wantedGeneration, hostName, timeout, traceIn, VespaVersion::fromString("1.2.3"), CompressionType::LZ4); + FRTConfigRequestV3 v3req(&conn, key, md5, currentGeneration, wantedGeneration, hostName, + timeout, traceIn, VespaVersion::fromString("1.2.3"), CompressionType::LZ4); + ASSERT_TRUE(v3req.verifyState(ConfigState(md5, 3))); + ASSERT_FALSE(v3req.verifyState(ConfigState(md5, 2))); + ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 3))); + ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 2))); + ConfigDefinition origDef(MyConfig::CONFIG_DEF_SCHEMA); FRT_RPCRequest * req = v3req.getRequest(); diff --git a/config/src/tests/subscriber/subscriber.cpp b/config/src/tests/subscriber/subscriber.cpp index 7613ba41234..39a537486cd 100644 --- a/config/src/tests/subscriber/subscriber.cpp +++ b/config/src/tests/subscriber/subscriber.cpp @@ -341,22 +341,22 @@ TEST_FF("requireThatHandlesAreMarkedAsChanged", MyManager, APIFixture(f1)) { ConfigSubscriber s(IConfigContext::SP(new APIFixture(f2))); ConfigHandle<FooConfig>::UP h1 = s.subscribe<FooConfig>("myid2"); ConfigHandle<BarConfig>::UP h2 = s.subscribe<BarConfig>("myid2"); - ASSERT_FALSE(s.nextConfig(0)); + EXPECT_FALSE(s.nextConfig(0)); f1.updateValue(0, createFooValue("foo"), 1); f1.updateValue(1, createFooValue("bar"), 1); - ASSERT_TRUE(s.nextConfig(100)); - ASSERT_TRUE(h1->isChanged()); - ASSERT_TRUE(h2->isChanged()); + EXPECT_TRUE(s.nextConfig(100)); + EXPECT_TRUE(h1->isChanged()); + EXPECT_TRUE(h2->isChanged()); - ASSERT_FALSE(s.nextConfig(100)); - ASSERT_FALSE(h1->isChanged()); - ASSERT_FALSE(h2->isChanged()); + EXPECT_FALSE(s.nextConfig(100)); + EXPECT_FALSE(h1->isChanged()); + EXPECT_FALSE(h2->isChanged()); f1.updateValue(0, createFooValue("bar"), 2); f1.updateGeneration(1, 2); - ASSERT_TRUE(s.nextConfig(100)); - ASSERT_TRUE(h1->isChanged()); - ASSERT_FALSE(h2->isChanged()); + EXPECT_TRUE(s.nextConfig(100)); + EXPECT_TRUE(h1->isChanged()); + EXPECT_FALSE(h2->isChanged()); } TEST_FF("requireThatNextGenerationMarksChanged", MyManager, APIFixture(f1)) { diff --git a/config/src/tests/subscription/subscription.cpp b/config/src/tests/subscription/subscription.cpp index 5d36753813c..a9e4c923e92 100644 --- a/config/src/tests/subscription/subscription.cpp +++ b/config/src/tests/subscription/subscription.cpp @@ -101,11 +101,11 @@ TEST_MT_F("requireThatNextUpdateReturnsInterrupted", 2, SubscriptionFixture(Conf TEST_F("Require that isChanged takes generation into account", SubscriptionFixture(ConfigKey::create<MyConfig>("myid"))) { - f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(), true, 1))); + f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(std::vector<vespalib::string>(), "a"), true, 1))); ASSERT_TRUE(f1.sub.nextUpdate(0, 0)); f1.sub.flip(); ASSERT_EQUAL(1, f1.sub.getLastGenerationChanged()); - f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(), true, 2))); + f1.holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(std::vector<vespalib::string>(), "b"), true, 2))); ASSERT_TRUE(f1.sub.nextUpdate(1, 0)); f1.sub.flip(); ASSERT_EQUAL(2, f1.sub.getLastGenerationChanged()); diff --git a/config/src/vespa/config/common/configholder.cpp b/config/src/vespa/config/common/configholder.cpp index e94510dcdea..e2e6ae87688 100644 --- a/config/src/vespa/config/common/configholder.cpp +++ b/config/src/vespa/config/common/configholder.cpp @@ -16,14 +16,16 @@ ConfigUpdate::UP ConfigHolder::provide() { vespalib::MonitorGuard guard(_monitor); - ConfigUpdate::UP ret(new ConfigUpdate(*_current)); - return ret; + return std::move(_current); } void ConfigHolder::handle(ConfigUpdate::UP update) { vespalib::MonitorGuard guard(_monitor); + if (_current) { + update->merge(*_current); + } _current = std::move(update); guard.broadcast(); } @@ -39,7 +41,7 @@ bool ConfigHolder::poll() { vespalib::MonitorGuard guard(_monitor); - return (_current.get() != NULL); + return static_cast<bool>(_current); } void diff --git a/config/src/vespa/config/common/configrequest.h b/config/src/vespa/config/common/configrequest.h index 395b09abcb0..efe1e56cdd7 100644 --- a/config/src/vespa/config/common/configrequest.h +++ b/config/src/vespa/config/common/configrequest.h @@ -24,15 +24,13 @@ public: ConfigRequest() { } virtual ~ConfigRequest() { } - virtual const ConfigKey & getKey() const = 0; - /** Abort a request. */ virtual bool abort() = 0; - virtual bool isAborted() const = 0; - virtual void setError(int errorCode) = 0; + virtual bool verifyState(const ConfigState & state) const = 0; + }; } diff --git a/config/src/vespa/config/common/configupdate.h b/config/src/vespa/config/common/configupdate.h index 7371d525266..9afdb9a5a40 100644 --- a/config/src/vespa/config/common/configupdate.h +++ b/config/src/vespa/config/common/configupdate.h @@ -19,6 +19,7 @@ public: const ConfigValue & getValue() const; bool hasChanged() const; int64_t getGeneration() const; + void merge(const ConfigUpdate & b) { _hasChanged = _hasChanged || b.hasChanged(); } private: ConfigValue _value; bool _hasChanged; diff --git a/config/src/vespa/config/frt/CMakeLists.txt b/config/src/vespa/config/frt/CMakeLists.txt index 9d0966bb09a..879b32684a7 100644 --- a/config/src/vespa/config/frt/CMakeLists.txt +++ b/config/src/vespa/config/frt/CMakeLists.txt @@ -8,9 +8,7 @@ vespa_add_library(config_frt OBJECT frtconfigresponse.cpp frtsourcefactory.cpp frtconfigagent.cpp - frtconfigrequestv2.cpp frtconfigrequestfactory.cpp - frtconfigresponsev2.cpp protocol.cpp slimeconfigrequest.cpp slimeconfigresponse.cpp diff --git a/config/src/vespa/config/frt/frtconfigagent.cpp b/config/src/vespa/config/frt/frtconfigagent.cpp index ff16ef77a1b..c1516a7de11 100644 --- a/config/src/vespa/config/frt/frtconfigagent.cpp +++ b/config/src/vespa/config/frt/frtconfigagent.cpp @@ -1,5 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "frtconfigagent.h" +#include "frtconfigrequestv3.h" #include <vespa/config/common/trace.h> #include <vespa/log/log.h> @@ -29,14 +30,14 @@ FRTConfigAgent::handleResponse(const ConfigRequest & request, ConfigResponse::UP LOG(spam, "current state for %s: generation %ld md5 %s", key.toString().c_str(), _configState.generation, _configState.md5.c_str()); } if (response->validateResponse() && !response->isError()) { - handleOKResponse(std::move(response)); + handleOKResponse(request, std::move(response)); } else { handleErrorResponse(request, std::move(response)); } } void -FRTConfigAgent::handleOKResponse(ConfigResponse::UP response) +FRTConfigAgent::handleOKResponse(const ConfigRequest & request, ConfigResponse::UP response) { _failedRequests = 0; response->fill(); @@ -45,8 +46,7 @@ FRTConfigAgent::handleOKResponse(ConfigResponse::UP response) } ConfigState newState = response->getConfigState(); - bool isNewGeneration = newState.isNewerGenerationThan(_configState); - if (isNewGeneration) { + if ( ! request.verifyState(newState)) { handleUpdatedGeneration(response->getKey(), newState, response->getValue()); } setWaitTime(_timingValues.successDelay, 1); @@ -57,22 +57,21 @@ void FRTConfigAgent::handleUpdatedGeneration(const ConfigKey & key, const ConfigState & newState, const ConfigValue & configValue) { if (LOG_WOULD_LOG(spam)) { - LOG(spam, "new generation %ld for key %s", newState.generation, key.toString().c_str()); + LOG(spam, "new generation %ld 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()); } - _configState.generation = newState.generation; - bool hasDifferentPayload = newState.hasDifferentPayloadFrom(_configState); - if (hasDifferentPayload) { - if (LOG_WOULD_LOG(spam)) { - LOG(spam, "new payload for key %s, existing md5(%s), new md5(%s)", key.toString().c_str(), _configState.md5.c_str(), newState.md5.c_str()); - } - _configState.md5 = newState.md5; + bool changed = false; + if (_latest.getMd5() != configValue.getMd5()) { _latest = configValue; + changed = true; } + _configState = newState; if (LOG_WOULD_LOG(spam)) { - LOG(spam, "updating holder for key %s, payload changed: %d", key.toString().c_str(), hasDifferentPayload ? 1 : 0); + LOG(spam, "updating holder for key %s,", key.toString().c_str()); } - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(_latest, hasDifferentPayload, _configState.generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(_latest, changed, newState.generation))); _numConfigured++; } diff --git a/config/src/vespa/config/frt/frtconfigagent.h b/config/src/vespa/config/frt/frtconfigagent.h index e3c362b5278..97edefbded7 100644 --- a/config/src/vespa/config/frt/frtconfigagent.h +++ b/config/src/vespa/config/frt/frtconfigagent.h @@ -33,7 +33,7 @@ public: const ConfigState & getConfigState() const override; private: void handleUpdatedGeneration(const ConfigKey & key, const ConfigState & newState, const ConfigValue & configValue); - void handleOKResponse(ConfigResponse::UP response); + void handleOKResponse(const ConfigRequest & request, ConfigResponse::UP response); void handleErrorResponse(const ConfigRequest & request, ConfigResponse::UP response); void setWaitTime(uint64_t delay, int multiplier); diff --git a/config/src/vespa/config/frt/frtconfigrequest.cpp b/config/src/vespa/config/frt/frtconfigrequest.cpp index aa792126aca..0845f7408de 100644 --- a/config/src/vespa/config/frt/frtconfigrequest.cpp +++ b/config/src/vespa/config/frt/frtconfigrequest.cpp @@ -2,9 +2,7 @@ #include "frtconfigrequest.h" #include "frtconfigresponse.h" #include "connection.h" -#include <vespa/fnet/frt/frt.h> -#include <vespa/config/common/configkey.h> -#include <vespa/config/common/configstate.h> +#include <vespa/fnet/frt/rpcrequest.h> namespace config { @@ -45,52 +43,4 @@ FRTConfigRequest::isAborted() const return (_request->GetErrorCode() == FRTE_RPC_ABORT); } -const vespalib::string FRTConfigRequestV1::REQUEST_TYPES = "sssssllsSi"; - -FRTConfigRequestV1::FRTConfigRequestV1(const ConfigKey & key, - Connection * connection, - const vespalib::string & configMd5, - int64_t generation, - int64_t serverTimeout) - : FRTConfigRequest(connection, key) -{ - _request->SetMethodName("config.v1.getConfig"); - _parameters.AddString(key.getDefName().c_str()); - _parameters.AddString(""); - _parameters.AddString(key.getDefMd5().c_str()); - _parameters.AddString(key.getConfigId().c_str()); - _parameters.AddString(configMd5.c_str()); - _parameters.AddInt64(generation); - _parameters.AddInt64(serverTimeout); - _parameters.AddString(key.getDefNamespace().c_str()); - const std::vector<vespalib::string> & schema(key.getDefSchema()); - FRT_StringValue * schemaValue = _parameters.AddStringArray(schema.size()); - for (size_t i = 0; i < schema.size(); i++) { - _parameters.SetString(&schemaValue[i], schema[i].c_str()); - } - _parameters.AddInt32(1); -} - -bool -FRTConfigRequestV1::verifyKey(const ConfigKey & key) const -{ - return (key.getDefName().compare(_parameters[0]._string._str) == 0 && - key.getDefNamespace().compare(_parameters[7]._string._str) == 0 && - key.getConfigId().compare(_parameters[3]._string._str) == 0 && - key.getDefMd5().compare(_parameters[2]._string._str) == 0); -} - -bool -FRTConfigRequestV1::verifyState(const ConfigState & state) const -{ - return (state.md5.compare(_parameters[4]._string._str) == 0 && - state.generation == static_cast<int64_t>(_parameters[5]._intval64)); -} - -ConfigResponse::UP -FRTConfigRequestV1::createResponse(FRT_RPCRequest * request) const -{ - return ConfigResponse::UP(new FRTConfigResponseV1(request)); -} - } // namespace config diff --git a/config/src/vespa/config/frt/frtconfigrequest.h b/config/src/vespa/config/frt/frtconfigrequest.h index e1d6b5590bd..061151d5f39 100644 --- a/config/src/vespa/config/frt/frtconfigrequest.h +++ b/config/src/vespa/config/frt/frtconfigrequest.h @@ -22,8 +22,6 @@ public: typedef std::unique_ptr<FRTConfigRequest> UP; FRTConfigRequest(Connection * connection, const ConfigKey & key); ~FRTConfigRequest(); - virtual bool verifyKey(const ConfigKey & key) const = 0; - virtual bool verifyState(const ConfigState & state) const = 0; bool abort() override; bool isAborted() const override; @@ -33,25 +31,11 @@ public: FRT_RPCRequest* getRequest() { return _request; } virtual ConfigResponse::UP createResponse(FRT_RPCRequest * request) const = 0; protected: - FRT_RPCRequest *_request; - FRT_Values & _parameters; + FRT_RPCRequest * _request; + FRT_Values & _parameters; private: - Connection * _connection; - const ConfigKey _key; -}; - -class FRTConfigRequestV1 : public FRTConfigRequest { -public: - FRTConfigRequestV1(const ConfigKey & key, - Connection * connection, - const vespalib::string & configMd5, - int64_t generation, - int64_t serverTimeout); - bool verifyKey(const ConfigKey & key) const override; - bool verifyState(const ConfigState & state) const override; - ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override; -private: - static const vespalib::string REQUEST_TYPES; + Connection * _connection; + const ConfigKey _key; }; } diff --git a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp index 9fe3e073b65..1f1ddb196b0 100644 --- a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp +++ b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp @@ -1,14 +1,9 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "frtconfigrequestfactory.h" -#include "frtconfigrequest.h" -#include "frtconfigrequestv2.h" #include "frtconfigrequestv3.h" -#include <vespa/config/common/trace.h> -#include <vespa/config/common/compressiontype.h> #include <vespa/vespalib/util/host_name.h> -#include <unistd.h> -#include <limits.h> +using std::make_unique; namespace config { @@ -28,14 +23,11 @@ FRTConfigRequestFactory::~FRTConfigRequestFactory() { } FRTConfigRequest::UP -FRTConfigRequestFactory::createConfigRequest(const ConfigKey & key, Connection * connection, const ConfigState & state, int64_t serverTimeout) const +FRTConfigRequestFactory::createConfigRequest(const ConfigKey & key, Connection * connection, + const ConfigState & state, int64_t serverTimeout) const { - if (1 == _protocolVersion) { - return FRTConfigRequest::UP(new FRTConfigRequestV1(key, connection, state.md5, state.generation, serverTimeout)); - } else if (2 == _protocolVersion) { - return FRTConfigRequest::UP(new FRTConfigRequestV2(connection, key, state.md5, state.generation, 0u, _hostName, serverTimeout, Trace(_traceLevel))); - } - return FRTConfigRequest::UP(new FRTConfigRequestV3(connection, key, state.md5, state.generation, 0u, _hostName, serverTimeout, Trace(_traceLevel), _vespaVersion, _compressionType)); + return make_unique<FRTConfigRequestV3>(connection, key, state.md5, state.generation, 0u, _hostName, + serverTimeout, Trace(_traceLevel), _vespaVersion, _compressionType); } } // namespace config diff --git a/config/src/vespa/config/frt/frtconfigrequestv2.cpp b/config/src/vespa/config/frt/frtconfigrequestv2.cpp deleted file mode 100644 index ddc79b830b3..00000000000 --- a/config/src/vespa/config/frt/frtconfigrequestv2.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "frtconfigrequestv2.h" -#include "frtconfigresponsev2.h" -#include "connection.h" -#include <vespa/config/common/trace.h> -#include <vespa/config/common/vespa_version.h> - -using namespace config::protocol; - -namespace config { - -FRTConfigRequestV2::FRTConfigRequestV2(Connection * connection, - const ConfigKey & key, - const vespalib::string & configMd5, - int64_t currentGeneration, - int64_t wantedGeneration, - const vespalib::string & hostName, - int64_t serverTimeout, - const Trace & trace) - : SlimeConfigRequest(connection, key, configMd5, currentGeneration, wantedGeneration, hostName, serverTimeout, trace, VespaVersion::getCurrentVersion(), 2, CompressionType::UNCOMPRESSED, "config.v2.getConfig") -{ -} - - - -ConfigResponse::UP -FRTConfigRequestV2::createResponse(FRT_RPCRequest * request) const -{ - return ConfigResponse::UP(new FRTConfigResponseV2(request)); -} - -} diff --git a/config/src/vespa/config/frt/frtconfigrequestv2.h b/config/src/vespa/config/frt/frtconfigrequestv2.h deleted file mode 100644 index 5f055153b8c..00000000000 --- a/config/src/vespa/config/frt/frtconfigrequestv2.h +++ /dev/null @@ -1,29 +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 "slimeconfigrequest.h" - -class FRT_Values; -class FRT_RPCRequest; - -namespace config { - -class ConfigKey; -class Connection; -class Trace; - -class FRTConfigRequestV2 : public SlimeConfigRequest { -public: - FRTConfigRequestV2(Connection * connection, - const ConfigKey & key, - const vespalib::string & configMd5, - int64_t currentGeneration, - int64_t wantedGeneration, - const vespalib::string & hostName, - int64_t serverTimeout, - const Trace & trace); - ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override; -}; - -} - diff --git a/config/src/vespa/config/frt/frtconfigresponse.cpp b/config/src/vespa/config/frt/frtconfigresponse.cpp index 98922ad4729..046416bcfd6 100644 --- a/config/src/vespa/config/frt/frtconfigresponse.cpp +++ b/config/src/vespa/config/frt/frtconfigresponse.cpp @@ -1,7 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "frtconfigresponse.h" -#include <vespa/fnet/frt/frt.h> +#include <vespa/fnet/frt/rpcrequest.h> namespace config { @@ -42,52 +42,4 @@ vespalib::string FRTConfigResponse::errorMessage() const { return _request->GetE int FRTConfigResponse::errorCode() const { return _request->GetErrorCode(); } bool FRTConfigResponse::isError() const { return _request->IsError(); } -// -// V1 Implementation -// -const vespalib::string FRTConfigResponseV1::RESPONSE_TYPES = "sssssilSs"; - -FRTConfigResponseV1::FRTConfigResponseV1(FRT_RPCRequest * request) - : FRTConfigResponse(request), - _key(), - _value() -{ -} - -FRTConfigResponseV1::~FRTConfigResponseV1() {} - -const vespalib::string & -FRTConfigResponseV1::getResponseTypes() const -{ - return RESPONSE_TYPES; -} - -void -FRTConfigResponseV1::fill() -{ - const std::vector<vespalib::string> payload(getPayLoad()); - _value = ConfigValue(payload, calculateContentMd5(payload)); - _key = readKey(); - _state = ConfigState(vespalib::string((*_returnValues)[4]._string._str), (*_returnValues)[6]._intval64); -} - -const ConfigKey -FRTConfigResponseV1::readKey() const -{ - return ConfigKey((*_returnValues)[3]._string._str, (*_returnValues)[0]._string._str, (*_returnValues)[8]._string._str, (*_returnValues)[2]._string._str); -} - -const std::vector<vespalib::string> -FRTConfigResponseV1::getPayLoad() const -{ - uint32_t numStrings = (*_returnValues)[7]._string_array._len; - FRT_StringValue *s = (*_returnValues)[7]._string_array._pt; - std::vector<vespalib::string> payload; - payload.reserve(numStrings); - for (uint32_t i = 0; i < numStrings; i++) { - payload.push_back(vespalib::string(s[i]._str)); - } - return payload; -} - } // namespace config diff --git a/config/src/vespa/config/frt/frtconfigresponse.h b/config/src/vespa/config/frt/frtconfigresponse.h index f829044e698..31811c6a38f 100644 --- a/config/src/vespa/config/frt/frtconfigresponse.h +++ b/config/src/vespa/config/frt/frtconfigresponse.h @@ -39,33 +39,5 @@ protected: FRT_Values * _returnValues; }; -class FRTConfigResponseV1 : public FRTConfigResponse { -private: - FRTConfigResponseV1& operator=(const FRTConfigResponseV1&); -public: - FRTConfigResponseV1(FRT_RPCRequest * request); - ~FRTConfigResponseV1(); - - const ConfigKey & getKey() const override { return _key; } - const ConfigValue & getValue() const override { return _value; } - const Trace & getTrace() const override { return _trace; } - - const ConfigState & getConfigState() const override { return _state; } - - void fill() override; - -private: - static const vespalib::string RESPONSE_TYPES; - - const std::vector<vespalib::string> getPayLoad() const; - const ConfigKey readKey() const; - const vespalib::string & getResponseTypes() const override; - - ConfigKey _key; - ConfigValue _value; - ConfigState _state; - Trace _trace; -}; - } // namespace config diff --git a/config/src/vespa/config/frt/frtconfigresponsev2.cpp b/config/src/vespa/config/frt/frtconfigresponsev2.cpp deleted file mode 100644 index 1527700f99f..00000000000 --- a/config/src/vespa/config/frt/frtconfigresponsev2.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "frtconfigresponsev2.h" -#include <vespa/fnet/frt/frt.h> - -using namespace vespalib; -using namespace vespalib::slime; -using namespace vespalib::slime::convenience; -using namespace config::protocol::v2; - -namespace config { - -class V2Payload : public protocol::Payload { -public: - V2Payload(const SlimePtr & data) - : _data(data) - {} - const Inspector & getSlimePayload() const override { - return extractPayload(*_data); - } -private: - SlimePtr _data; -}; - -const vespalib::string FRTConfigResponseV2::RESPONSE_TYPES = "s"; - -FRTConfigResponseV2::FRTConfigResponseV2(FRT_RPCRequest * request) - : SlimeConfigResponse(request) -{ -} - -const vespalib::string & -FRTConfigResponseV2::getResponseTypes() const -{ - return RESPONSE_TYPES; -} - -const ConfigValue -FRTConfigResponseV2::readConfigValue() const -{ - vespalib::string md5(_data->get()[RESPONSE_CONFIG_MD5].asString().make_string()); - return ConfigValue(PayloadPtr(new V2Payload(_data)), md5); -} - -} // namespace config diff --git a/config/src/vespa/config/frt/frtconfigresponsev2.h b/config/src/vespa/config/frt/frtconfigresponsev2.h deleted file mode 100644 index 89d1a9157e3..00000000000 --- a/config/src/vespa/config/frt/frtconfigresponsev2.h +++ /dev/null @@ -1,28 +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 "slimeconfigresponse.h" -#include <vespa/config/common/configvalue.h> - -class FRT_RPCRequest; -class FRT_Values; - -namespace config { - -/** - * Baseclass for config responses. - */ -class FRTConfigResponseV2 : public SlimeConfigResponse { -private: - FRTConfigResponseV2& operator=(const FRTConfigResponseV2&); -public: - FRTConfigResponseV2(FRT_RPCRequest * request); - -private: - static const vespalib::string RESPONSE_TYPES; - const vespalib::string & getResponseTypes() const override; - const ConfigValue readConfigValue() const override; -}; - -} // namespace config - diff --git a/config/src/vespa/config/frt/slimeconfigrequest.cpp b/config/src/vespa/config/frt/slimeconfigrequest.cpp index 12dbbab2eb7..27ac39ae56a 100644 --- a/config/src/vespa/config/frt/slimeconfigrequest.cpp +++ b/config/src/vespa/config/frt/slimeconfigrequest.cpp @@ -40,19 +40,10 @@ SlimeConfigRequest::SlimeConfigRequest(Connection * connection, } bool -SlimeConfigRequest::verifyKey(const ConfigKey & key) const -{ - return (key.getDefName().compare(_parameters[0]._string._str) == 0 && - key.getDefNamespace().compare(_parameters[7]._string._str) == 0 && - key.getConfigId().compare(_parameters[3]._string._str) == 0 && - key.getDefMd5().compare(_parameters[2]._string._str) == 0); -} - -bool SlimeConfigRequest::verifyState(const ConfigState & state) const { - return (state.md5.compare(_parameters[4]._string._str) == 0 && - state.generation == static_cast<int64_t>(_parameters[5]._intval64)); + return (state.md5.compare(_data[REQUEST_CONFIG_MD5].asString().make_stringref()) == 0 && + state.generation == _data[REQUEST_CURRENT_GENERATION].asLong()); } void diff --git a/config/src/vespa/config/frt/slimeconfigrequest.h b/config/src/vespa/config/frt/slimeconfigrequest.h index d616876780e..625d70c094b 100644 --- a/config/src/vespa/config/frt/slimeconfigrequest.h +++ b/config/src/vespa/config/frt/slimeconfigrequest.h @@ -30,7 +30,6 @@ public: const CompressionType & compressionType, const vespalib::string & methodName); ~SlimeConfigRequest() {} - bool verifyKey(const ConfigKey & key) const override; bool verifyState(const ConfigState & state) const override; virtual ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override = 0; private: diff --git a/config/src/vespa/config/subscription/configsubscription.cpp b/config/src/vespa/config/subscription/configsubscription.cpp index d97ddfd57fb..efa3f5a02e3 100644 --- a/config/src/vespa/config/subscription/configsubscription.cpp +++ b/config/src/vespa/config/subscription/configsubscription.cpp @@ -28,9 +28,10 @@ ConfigSubscription::~ConfigSubscription() bool ConfigSubscription::nextUpdate(int64_t generation, uint64_t timeoutInMillis) { - if (_closed || !_holder->poll()) + if (_closed || !_holder->poll()) { return false; - _next.reset(_holder->provide().release()); + } + _next = _holder->provide(); if (isGenerationNewer(_next->getGeneration(), generation)) { return true; } @@ -38,9 +39,15 @@ ConfigSubscription::nextUpdate(int64_t generation, uint64_t timeoutInMillis) } bool +ConfigSubscription::hasGenerationChanged() const +{ + return (!_closed && _next && ((_current && (_current->getGeneration() != _next->getGeneration())) || ! _current)); +} + +bool ConfigSubscription::hasChanged() const { - return (!_closed && (_next->hasChanged() || _current.get() == NULL)); + return (!_closed && _next && ((_next->hasChanged() && _current && (_current->getValue() != _next->getValue())) || ! _current)); } int64_t @@ -88,7 +95,7 @@ ConfigSubscription::flip() { bool change = hasChanged(); if (change) { - _current.reset(_next.release()); + _current = std::move(_next); _lastGenerationChanged = _current->getGeneration(); } else { _current.reset(new ConfigUpdate(_current->getValue(), false, _next->getGeneration())); @@ -96,13 +103,15 @@ ConfigSubscription::flip() _isChanged = change; } -ConfigValue +const ConfigValue & ConfigSubscription::getConfig() const { - if (_closed) + if (_closed) { throw ConfigRuntimeException("Subscription is closed, config no longer available"); - if (_current.get() == NULL) + } + if ( ! _current) { throw ConfigRuntimeException("No configuration available"); + } return _current->getValue(); } diff --git a/config/src/vespa/config/subscription/configsubscription.h b/config/src/vespa/config/subscription/configsubscription.h index 04c3da114fd..56b65922a61 100644 --- a/config/src/vespa/config/subscription/configsubscription.h +++ b/config/src/vespa/config/subscription/configsubscription.h @@ -28,7 +28,7 @@ public: * * @return the current ConfigValue. */ - ConfigValue getConfig() const; + const ConfigValue & getConfig() const; /** * Checks whether or not the config has changed. @@ -48,6 +48,7 @@ public: bool nextUpdate(int64_t generation, uint64_t timeoutInMillis); int64_t getGeneration() const; bool hasChanged() const; + bool hasGenerationChanged() const; void flip(); void reset(); void close(); diff --git a/config/src/vespa/config/subscription/configsubscriptionset.cpp b/config/src/vespa/config/subscription/configsubscriptionset.cpp index f6268c8a84a..ab38ba18351 100644 --- a/config/src/vespa/config/subscription/configsubscriptionset.cpp +++ b/config/src/vespa/config/subscription/configsubscriptionset.cpp @@ -38,10 +38,6 @@ ConfigSubscriptionSet::acquireSnapshot(uint64_t timeoutInMillis, bool ignoreChan int64_t lastGeneration = _currentGeneration; bool inSync = false; - for (const auto & subscription : _subscriptionList) { - subscription->reset(); - } - LOG(debug, "Going into nextConfig loop, time left is %d", timeLeft); while (_state != CLOSED && timeLeft >= 0 && !inSync) { size_t numChanged = 0; @@ -52,8 +48,10 @@ ConfigSubscriptionSet::acquireSnapshot(uint64_t timeoutInMillis, bool ignoreChan // Run nextUpdate on all subscribers to get them in sync. for (const auto & subscription : _subscriptionList) { - if (!subscription->nextUpdate(_currentGeneration, timeLeft)) - break; + if (!subscription->nextUpdate(_currentGeneration, timeLeft) && !subscription->hasGenerationChanged()) { + subscription->reset(); + continue; + } const ConfigKey & key(subscription->getKey()); if (subscription->hasChanged()) { diff --git a/configd/src/apps/sentinel/config-handler.cpp b/configd/src/apps/sentinel/config-handler.cpp index e5222b0350b..67216954f51 100644 --- a/configd/src/apps/sentinel/config-handler.cpp +++ b/configd/src/apps/sentinel/config-handler.cpp @@ -56,7 +56,8 @@ ConfigHandler::ConfigHandler() _outputConnections(), _boundPort(0), _commandSocket(listen(0)), - _startMetrics() + _startMetrics(), + _stateApi(_startMetrics.producer) { _startMetrics.startedTime = time(nullptr); } @@ -395,61 +396,10 @@ ConfigHandler::handleCommand(CommandConnection *c) } } -namespace { - -void -fillRestarts(vespalib::SimpleMetricSnapshot &snapshot, unsigned long restarts) -{ - snapshot.addCount("sentinel.restarts", - "how many times sentinel restarted a service", - restarts); -} - -void -fillTotalRestarts(vespalib::SimpleMetricSnapshot &snapshot, - unsigned long totalRestarts) -{ - snapshot.addGauge("sentinel.totalRestarts", - "how many times sentinel restarted a service since " - "sentinel start", - totalRestarts); -} - -void -fillRunning(vespalib::SimpleMetricSnapshot &snapshot, unsigned long running) -{ - snapshot.addGauge("sentinel.running", - "how many services the sentinel has running currently", - running); -} - -void -fillUptime(vespalib::SimpleMetricSnapshot &snapshot, long uptime) -{ - snapshot.addGauge("sentinel.uptime", - "how many seconds has the sentinel been running", - uptime); -} - -} // namespace <unnamed> - void ConfigHandler::updateMetrics() { - time_t now = time(nullptr); - vespalib::SimpleMetricSnapshot snapshot(_startMetrics.snapshotStart, _startMetrics.snapshotEnd); - fillRestarts(snapshot, _startMetrics.totalRestartsLastSnapshot); - fillTotalRestarts(snapshot, _startMetrics.totalRestartsCounter); - fillRunning(snapshot, _startMetrics.currentlyRunningServices); - fillUptime(snapshot, now - _startMetrics.startedTime); - _stateApi.myMetrics.setMetrics(snapshot.asString()); - - vespalib::SimpleMetricSnapshot totals(_startMetrics.startedTime, now); - fillRestarts(totals, _startMetrics.totalRestartsCounter); - fillRunning(totals, _startMetrics.currentlyRunningServices); - fillUptime(totals, now - _startMetrics.startedTime); - _stateApi.myMetrics.setTotalMetrics(totals.asString()); - + _startMetrics.maybeLog(); } void diff --git a/configd/src/apps/sentinel/metrics.cpp b/configd/src/apps/sentinel/metrics.cpp index 42c61c06b50..df1b0076001 100644 --- a/configd/src/apps/sentinel/metrics.cpp +++ b/configd/src/apps/sentinel/metrics.cpp @@ -4,18 +4,33 @@ #include <vespa/log/log.h> LOG_SETUP(".metrics"); +#include <vespa/vespalib/metrics/simple_metrics.h> + namespace config { namespace sentinel { +using vespalib::metrics::SimpleMetricsManager; +using vespalib::metrics::SimpleManagerConfig; + StartMetrics::StartMetrics() - : currentlyRunningServices(0), totalRestartsCounter(0), totalRestartsLastPeriod(1), - lastLoggedTime(0), - totalRestartsLastSnapshot(0), - snapshotStart(0), - snapshotEnd(0) + : metrics(SimpleMetricsManager::create(SimpleManagerConfig())), + producer(metrics), + currentlyRunningServices(0), + totalRestartsCounter(0), + totalRestartsLastPeriod(1), + startedTime(time(nullptr)), + lastLoggedTime(startedTime - 55), + sentinel_restarts(metrics->counter("sentinel.restarts", + "how many times sentinel restarted a service")), + sentinel_totalRestarts(metrics->gauge("sentinel.totalRestarts", + "how many times sentinel restarted a service since sentinel start")), + sentinel_running(metrics->gauge("sentinel.running", + "how many services the sentinel has running currently")), + sentinel_uptime(metrics->gauge("sentinel.uptime", + "how many seconds has the sentinel been running")) { - snapshotEnd = time(nullptr); - lastLoggedTime = snapshotEnd - 55; + // account for the sentinel itself restarting + sentinel_restarts.add(); } void @@ -29,9 +44,6 @@ StartMetrics::output() void StartMetrics::reset(unsigned long curTime) { - totalRestartsLastSnapshot = totalRestartsLastPeriod; - snapshotStart = snapshotEnd; - snapshotEnd = curTime; totalRestartsLastPeriod = 0; lastLoggedTime = curTime; } @@ -40,6 +52,9 @@ void StartMetrics::maybeLog() { uint32_t curTime = time(nullptr); + sentinel_totalRestarts.sample(totalRestartsCounter); + sentinel_running.sample(currentlyRunningServices); + sentinel_uptime.sample(curTime - startedTime); if (curTime > lastLoggedTime + 59) { output(); reset(curTime); diff --git a/configd/src/apps/sentinel/metrics.h b/configd/src/apps/sentinel/metrics.h index dd24bb2280d..2378a055663 100644 --- a/configd/src/apps/sentinel/metrics.h +++ b/configd/src/apps/sentinel/metrics.h @@ -2,20 +2,29 @@ #pragma once #include <sys/time.h> +#include <vespa/vespalib/metrics/simple_metrics.h> namespace config::sentinel { +using vespalib::metrics::Counter; +using vespalib::metrics::Gauge; +using vespalib::metrics::MetricsManager; + struct StartMetrics { + std::shared_ptr<MetricsManager> metrics; + vespalib::metrics::Producer producer; unsigned long currentlyRunningServices; unsigned long totalRestartsCounter; unsigned long totalRestartsLastPeriod; - long lastLoggedTime; - unsigned long totalRestartsLastSnapshot; - long snapshotStart; - long snapshotEnd; long startedTime; + long lastLoggedTime; + Counter sentinel_restarts; + Gauge sentinel_totalRestarts; + Gauge sentinel_running; + Gauge sentinel_uptime; StartMetrics(); + ~StartMetrics() {} void output(); void reset(unsigned long curTime); @@ -23,4 +32,3 @@ struct StartMetrics { }; } - diff --git a/configd/src/apps/sentinel/service.cpp b/configd/src/apps/sentinel/service.cpp index 2f13a05eb4f..3c762a957ec 100644 --- a/configd/src/apps/sentinel/service.cpp +++ b/configd/src/apps/sentinel/service.cpp @@ -239,6 +239,7 @@ Service::start() // ensureChildRuns(pipes[0]); // This will wait until the execl goes through setState(RUNNING); _metrics.currentlyRunningServices++; + _metrics.sentinel_running.sample(_metrics.currentlyRunningServices); close(pipes[0]); // close reading end using ns_log::LLParser; @@ -314,6 +315,7 @@ Service::youExited(int status) setState(FAILED); } _metrics.currentlyRunningServices--; + _metrics.sentinel_running.sample(_metrics.currentlyRunningServices); if (_state == TERMINATING) { setState(TERMINATED); @@ -326,6 +328,7 @@ Service::youExited(int status) setState(READY); _metrics.totalRestartsCounter++; _metrics.totalRestartsLastPeriod++; + _metrics.sentinel_restarts.add(); start(); } } diff --git a/configd/src/apps/sentinel/state-api.h b/configd/src/apps/sentinel/state-api.h index 879563b79a5..ca080a35865 100644 --- a/configd/src/apps/sentinel/state-api.h +++ b/configd/src/apps/sentinel/state-api.h @@ -3,9 +3,9 @@ #pragma once #include <vespa/vespalib/net/state_api.h> -#include <vespa/vespalib/net/simple_metrics_producer.h> #include <vespa/vespalib/net/simple_health_producer.h> #include <vespa/vespalib/net/simple_component_config_producer.h> +#include <vespa/vespalib/metrics/simple_metrics.h> namespace config { namespace sentinel { @@ -13,11 +13,12 @@ namespace sentinel { struct StateApi { vespalib::string host_and_port; vespalib::SimpleHealthProducer myHealth; - vespalib::SimpleMetricsProducer myMetrics; vespalib::SimpleComponentConfigProducer myComponents; vespalib::StateApi myStateApi; - StateApi() : myStateApi(myHealth, myMetrics, myComponents) {} + StateApi(vespalib::metrics::Producer &myMetrics) + : myStateApi(myHealth, myMetrics, myComponents) + {} vespalib::string get(const char *path) const; void bound(int port); diff --git a/configdefinitions/pom.xml b/configdefinitions/pom.xml index 92980a18899..487c43ed3b3 100644 --- a/configdefinitions/pom.xml +++ b/configdefinitions/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>configdefinitions</artifactId> <packaging>container-plugin</packaging> diff --git a/configdefinitions/src/vespa/configserver.def b/configdefinitions/src/vespa/configserver.def index c13665342ef..cbc2317da2d 100644 --- a/configdefinitions/src/vespa/configserver.def +++ b/configdefinitions/src/vespa/configserver.def @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. namespace=cloud.config + rpcport int default=19070 httpport int default=19071 numthreads int default=16 @@ -43,4 +44,8 @@ dockerRegistry string default="" dockerVespaBaseImage string default="" # Athenz config -loadBalancerAddress string default=""
\ No newline at end of file +loadBalancerAddress string default="" + +# File distribution +disableFiledistributor bool default=false +usechunkedtransfer bool default=true diff --git a/configgen/pom.xml b/configgen/pom.xml index 6e550ec7321..e8de114f8d5 100644 --- a/configgen/pom.xml +++ b/configgen/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>configgen</artifactId> <packaging>jar</packaging> diff --git a/configserver/pom.xml b/configserver/pom.xml index 30d92dc7650..f5aa8ea3118 100644 --- a/configserver/pom.xml +++ b/configserver/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>configserver</artifactId> <packaging>container-plugin</packaging> diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index 13401d4a4a6..a4dd943aa47 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -112,6 +112,18 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye * Creates a new deployment from the active application, if available. * * @param application the active application to be redeployed + * @return a new deployment from the local active, or empty if a local active application + * was not present for this id (meaning it either is not active or active on another + * node in the config server cluster) + */ + public Optional<com.yahoo.config.provision.Deployment> deployFromLocalActive(ApplicationId application) { + return deployFromLocalActive(application, Duration.ofSeconds(configserverConfig.zookeeper().barrierTimeout()).plus(Duration.ofSeconds(5))); + } + + /** + * Creates a new deployment from the active application, if available. + * + * @param application the active application to be redeployed * @param timeout the timeout to use for each individual deployment operation * @return a new deployment from the local active, or empty if a local active application * was not present for this id (meaning it either is not active or active on another @@ -352,7 +364,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye private void redeployApplication(ApplicationId applicationId, Deployer deployer, ExecutorService deploymentExecutor) { log.log(LogLevel.DEBUG, () -> "Redeploying " + applicationId); - deployer.deployFromLocalActive(applicationId, Duration.ofMinutes(30)) + deployer.deployFromLocalActive(applicationId) .ifPresent(deployment -> deploymentExecutor.execute(() -> { try { deployment.activate(); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java index 58c61134bc4..6ab98f5af1c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.Deployer; +import com.yahoo.container.jdisc.state.StateMonitor; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.rpc.RpcServer; import com.yahoo.vespa.config.server.version.VersionState; @@ -23,17 +24,19 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable private final Thread serverThread; private final Deployer deployer; private final VersionState versionState; + private final StateMonitor stateMonitor; // The tenants object is injected so that all initial requests handlers are // added to the rpc server before it starts answering rpc requests. - @SuppressWarnings("UnusedParameters") + @SuppressWarnings("WeakerAccess") @Inject public ConfigServerBootstrap(ApplicationRepository applicationRepository, RpcServer server, - Deployer deployer, VersionState versionState) { + Deployer deployer, VersionState versionState, StateMonitor stateMonitor) { this.applicationRepository = applicationRepository; this.server = server; this.deployer = deployer; this.versionState = versionState; + this.stateMonitor = stateMonitor; this.serverThread = new Thread(this, "configserver main"); serverThread.start(); } @@ -62,9 +65,11 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable log.log(LogLevel.INFO, "All applications redeployed"); } versionState.saveNewVersion(); + stateMonitor.status(StateMonitor.Status.up); log.log(LogLevel.DEBUG, "Starting RPC server"); server.run(); log.log(LogLevel.DEBUG, "RPC server stopped"); + stateMonitor.status(StateMonitor.Status.down); } } 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 c0c9c309576..4502cc7e223 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 @@ -130,8 +130,8 @@ public class ModelContextImpl implements ModelContext { public Version wantedNodeVespaVersion() { return wantedNodeVespaVersion; } /** - * @author lulf - */ + * @author Ulf Lilleengen + */ public static class Properties implements ModelContext.Properties { private final ApplicationId applicationId; @@ -141,6 +141,7 @@ public class ModelContextImpl implements ModelContext { private final boolean hostedVespa; private final Zone zone; private final Set<Rotation> rotations; + private final boolean disableFileDistributor; public Properties(ApplicationId applicationId, boolean multitenant, @@ -148,7 +149,8 @@ public class ModelContextImpl implements ModelContext { HostName loadBalancerName, boolean hostedVespa, Zone zone, - Set<Rotation> rotations) { + Set<Rotation> rotations, + boolean disableFileDistributor) { this.applicationId = applicationId; this.multitenant = multitenant; this.configServerSpecs = configServerSpecs; @@ -156,6 +158,7 @@ public class ModelContextImpl implements ModelContext { this.hostedVespa = hostedVespa; this.zone = zone; this.rotations = rotations; + this.disableFileDistributor = disableFileDistributor; } @Override @@ -189,9 +192,10 @@ public class ModelContextImpl implements ModelContext { } @Override - public Set<Rotation> rotations() { - return rotations; - } + public Set<Rotation> rotations() { return rotations; } + + @Override + public boolean disableFileDistributor() { return disableFileDistributor; } } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java index 588f2d1d63f..1046ed93491 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java @@ -3,22 +3,45 @@ package com.yahoo.vespa.config.server.filedistribution; import com.yahoo.config.FileReference; import com.yahoo.config.model.api.FileDistribution; +import com.yahoo.jrt.Request; +import com.yahoo.jrt.Spec; +import com.yahoo.jrt.StringArray; +import com.yahoo.jrt.Supervisor; +import com.yahoo.jrt.Target; +import com.yahoo.jrt.Transport; +import com.yahoo.log.LogLevel; import java.util.Collection; import java.util.Set; +import java.util.logging.Logger; +/** + * @author baldersheim + */ public class CombinedLegacyDistribution implements FileDistribution { + private final static Logger log = Logger.getLogger(CombinedLegacyDistribution.class.getName()); + + private final Supervisor supervisor = new Supervisor(new Transport()); private final FileDistribution legacy; + private final boolean disableFileDistributor; - CombinedLegacyDistribution(FileDBHandler legacy) { + CombinedLegacyDistribution(FileDBHandler legacy, boolean disableFileDistributor) { this.legacy = legacy; + this.disableFileDistributor = disableFileDistributor; } + @Override public void sendDeployedFiles(String hostName, Set<FileReference> fileReferences) { legacy.sendDeployedFiles(hostName, fileReferences); } @Override + public void startDownload(String hostName, Set<FileReference> fileReferences) { + if (disableFileDistributor) + startDownloadingFileReferences(hostName, fileReferences); + } + + @Override public void reloadDeployFileDistributor() { legacy.reloadDeployFileDistributor(); } @@ -27,4 +50,18 @@ public class CombinedLegacyDistribution implements FileDistribution { public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) { legacy.removeDeploymentsThatHaveDifferentApplicationId(targetHostnames); } + + // Notifies config proxy which file references it should start downloading. It's OK if the call does not succeed, + // as downloading will then start synchronously when a service requests a file reference instead + private void startDownloadingFileReferences(String hostName, Set<FileReference> fileReferences) { + Target target = supervisor.connect(new Spec(hostName, 19090)); + double timeout = 0.1; + Request request = new Request("filedistribution.setFileReferencesToDownload"); + request.parameters().add(new StringArray(fileReferences.stream().map(FileReference::value).toArray(String[]::new))); + log.log(LogLevel.DEBUG, "Executing " + request.methodName() + " against " + target.toString()); + target.invokeSync(request, timeout); + if (request.isError()) { + log.log(LogLevel.INFO, request.methodName() + " failed: " + request.errorCode() + " (" + request.errorMessage() + ")"); + } + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java index f0ce6104496..9b3f4c39a45 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java @@ -10,9 +10,8 @@ import java.util.*; /** * Implements invoker of filedistribution using manager with JNI. * - * @author tonytv - * @author lulf - * @since 5.1.14 + * @author Tony Vaagenes + * @author Ulf Lilleengen */ public class FileDBHandler implements FileDistribution { private final FileDistributionManager manager; @@ -31,6 +30,11 @@ public class FileDBHandler implements FileDistribution { } @Override + public void startDownload(String hostName, Set<FileReference> fileReferences) { + throw new UnsupportedOperationException("Not valid for this Filedistribution implementation"); + } + + @Override public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) { manager.removeDeploymentsThatHaveDifferentApplicationId(targetHostnames); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java index 7d0ba6cd9bd..e991341b616 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java @@ -5,16 +5,21 @@ package com.yahoo.vespa.config.server.filedistribution; import com.yahoo.config.FileReference; import com.yahoo.config.model.api.FileDistribution; import com.yahoo.io.IOUtils; +import com.yahoo.log.LogLevel; import com.yahoo.text.Utf8; import net.jpountz.xxhash.XXHash64; import net.jpountz.xxhash.XXHashFactory; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.logging.Logger; public class FileDirectory { @@ -30,7 +35,7 @@ public class FileDirectory { try { ensureRootExist(); } catch (IllegalArgumentException e) { - log.warning("Failed creating directory in constructor, will retry on demand : " + e.toString()); + log.log(LogLevel.WARNING, "Failed creating directory in constructor, will retry on demand : " + e.toString()); } } @@ -65,12 +70,8 @@ public class FileDirectory { throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + "' is not a directory."); } File [] files = dir.listFiles(new Filter()); - if (files.length != 1) { - StringBuilder msg = new StringBuilder(); - for (File f: files) { - msg.append(f.getName()).append("\n"); - } - throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + " does not contain exactly one file, but [" + msg.toString() + "]"); + if (files == null || files.length == 0) { + throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + " does not contain any files"); } return files[0]; } @@ -91,24 +92,28 @@ public class FileDirectory { } } - public FileReference addFile(File source, FileReference reference) { + FileReference addFile(File source, FileReference reference) { ensureRootExist(); try { + logfileInfo(source); File destinationDir = new File(root, reference.value()); + Path tempDestinationDir = Files.createTempDirectory(root.toPath(), "writing"); + File destination = new File(tempDestinationDir.toFile(), source.getName()); if (!destinationDir.exists()) { destinationDir.mkdir(); - Path tempDestinationDir = Files.createTempDirectory(root.toPath(), "writing"); - File destination = new File(tempDestinationDir.toFile(), source.getName()); - if (source.isDirectory()) - IOUtils.copyDirectory(source, destination); - else - IOUtils.copy(source, destination); + log.log(LogLevel.DEBUG, "file reference ' " + reference.value() + "', source: " + source.getAbsolutePath() ); + if (source.isDirectory()) { + log.log(LogLevel.DEBUG, "Copying source " + source.getAbsolutePath() + " to " + destination.getAbsolutePath()); + IOUtils.copyDirectory(source, destination, -1); + } else + copyFile(source, destination); if (!destinationDir.exists()) { + log.log(LogLevel.DEBUG, "Moving from " + tempDestinationDir + " to " + destinationDir.getAbsolutePath()); if ( ! tempDestinationDir.toFile().renameTo(destinationDir)) { - log.warning("Failed moving '" + tempDestinationDir.toFile().getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'."); + log.log(LogLevel.WARNING, "Failed moving '" + tempDestinationDir.toFile().getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'."); } } else { - IOUtils.copyDirectory(tempDestinationDir.toFile(), destinationDir, 1); + IOUtils.copyDirectory(tempDestinationDir.toFile(), destinationDir, -1); } IOUtils.recursiveDeleteDir(tempDestinationDir.toFile()); } @@ -117,4 +122,18 @@ public class FileDirectory { throw new IllegalArgumentException(e); } } + + private void logfileInfo(File file ) throws IOException { + BasicFileAttributes basicFileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + log.log(LogLevel.DEBUG, "Adding file " + file.getAbsolutePath() + " (created " + basicFileAttributes.creationTime() + + ", modified " + basicFileAttributes.lastModifiedTime() + + ", size " + basicFileAttributes.size() + ")"); + } + + private static void copyFile(File source, File dest) throws IOException { + try (FileChannel sourceChannel = new FileInputStream(source).getChannel(); + FileChannel destChannel = new FileOutputStream(dest).getChannel()) { + destChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); + } + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java index 59c3a54897d..38fa3087f88 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java @@ -12,8 +12,7 @@ import java.util.concurrent.locks.Lock; /** * Provides file distribution registry and invoker. * - * @author lulf - * @since 5.1.14 + * @author Ulf Lilleengen */ public class FileDistributionProvider { @@ -37,18 +36,19 @@ public class FileDistributionProvider { } public FileDistributionProvider(File applicationDir, String zooKeepersSpec, - String applicationId, Lock fileDistributionLock) - { + String applicationId, Lock fileDistributionLock, + boolean disableFileDistributor) { ensureDirExists(FileDistribution.getDefaultFileDBPath()); final FileDistributionManager manager = new FileDistributionManager( FileDistribution.getDefaultFileDBPath(), applicationDir, zooKeepersSpec, applicationId, fileDistributionLock); - this.fileDistribution = new CombinedLegacyDistribution(new FileDBHandler(manager)); + this.fileDistribution = new CombinedLegacyDistribution(new FileDBHandler(manager), disableFileDistributor); this.fileRegistry = new CombinedLegacyRegistry(new FileDBRegistry(new ManagerWrapper(manager)), new FileDBRegistry(new ApplicationFileManager(applicationDir, new FileDirectory()))); } - public FileDistributionProvider(FileRegistry fileRegistry, FileDistribution fileDistribution) { + // For testing only + FileDistributionProvider(FileRegistry fileRegistry, FileDistribution fileDistribution) { this.fileRegistry = fileRegistry; this.fileDistribution = fileDistribution; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java index 9dc94c9fe93..a9c56cf99d0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java @@ -2,24 +2,55 @@ package com.yahoo.vespa.config.server.filedistribution; import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.FileReference; import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.subscription.ConfigSourceSet; -import com.yahoo.io.IOUtils; +import com.yahoo.jrt.Int32Value; +import com.yahoo.jrt.Request; +import com.yahoo.jrt.StringValue; +import com.yahoo.jrt.Supervisor; +import com.yahoo.jrt.Transport; +import com.yahoo.log.LogLevel; +import com.yahoo.net.HostName; +import com.yahoo.vespa.config.Connection; +import com.yahoo.vespa.config.ConnectionPool; import com.yahoo.vespa.config.JRTConnectionPool; +import com.yahoo.vespa.config.server.ConfigServerSpec; +import com.yahoo.vespa.filedistribution.CompressedFileReference; import com.yahoo.vespa.filedistribution.FileDownloader; +import com.yahoo.vespa.filedistribution.FileReferenceData; +import com.yahoo.vespa.filedistribution.FileReferenceDataBlob; +import com.yahoo.vespa.filedistribution.LazyFileReferenceData; import java.io.File; import java.io.IOException; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; +import java.util.stream.Collectors; public class FileServer { private static final Logger log = Logger.getLogger(FileServer.class.getName()); + private final FileDirectory root; - private final ExecutorService executor; - private final FileDownloader downloader = new FileDownloader(new JRTConnectionPool(ConfigSourceSet.createDefault())); + private final ExecutorService pushExecutor; + private final ExecutorService pullExecutor; + private final FileDownloader downloader; + + private enum FileApiErrorCodes { + OK(0, "OK"), + NOT_FOUND(1, "Filereference not found"); + private final int code; + private final String description; + FileApiErrorCodes(int code, String description) { + this.code = code; + this.description = description; + } + int getCode() { return code; } + String getDescription() { return description; } + } public static class ReplayStatus { private final int code; @@ -34,22 +65,26 @@ public class FileServer { } public interface Receiver { - void receive(FileReference reference, String filename, byte [] content, ReplayStatus status); + void receive(FileReferenceData fileData, ReplayStatus status); } @Inject - public FileServer() { - this(FileDistribution.getDefaultFileDBPath()); + public FileServer(ConfigserverConfig configserverConfig) { + this(createConnectionPool(configserverConfig), FileDistribution.getDefaultFileDBPath()); } + // For testing only public FileServer(File rootDir) { - this(rootDir, Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())); + this(new EmptyConnectionPool(), rootDir); } - public FileServer(File rootDir, ExecutorService executor) { + private FileServer(ConnectionPool connectionPool, File rootDir) { + this.downloader = new FileDownloader(connectionPool); this.root = new FileDirectory(rootDir); - this.executor = executor; + this.pushExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + this.pullExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } + public boolean hasFile(String fileName) { return hasFile(new FileReference(fileName)); } @@ -66,32 +101,102 @@ public class FileServer { File file = root.getFile(reference); if (file.exists()) { - executor.execute(() -> serveFile(reference, target)); + pushExecutor.execute(() -> serveFile(reference, target)); } return false; } private void serveFile(FileReference reference, Receiver target) { File file = root.getFile(reference); - // TODO remove once verified in system tests. - log.info("Start serving reference '" + reference.value() + "' with file '" + file.getAbsolutePath() + "'"); - byte [] blob = new byte [0]; + log.log(LogLevel.DEBUG, "Start serving reference '" + reference.value() + "' with file '" + file.getAbsolutePath() + "'"); boolean success = false; String errorDescription = "OK"; + FileReferenceData fileData = FileReferenceDataBlob.empty(reference, file.getName()); try { - blob = IOUtils.readFileBytes(file); + fileData = readFileReferenceData(reference); success = true; } catch (IOException e) { - errorDescription = "For file reference '" + reference.value() + "' I failed reading file '" + file.getAbsolutePath() + "'"; - log.warning(errorDescription + "for sending to '" + target.toString() + "'. " + e.toString()); + errorDescription = "For file reference '" + reference.value() + "': failed reading file '" + file.getAbsolutePath() + "'"; + log.warning(errorDescription + " for sending to '" + target.toString() + "'. " + e.toString()); } - target.receive(reference, file.getName(), blob, - new ReplayStatus(success ? 0 : 1, success ? "OK" : errorDescription)); - // TODO remove once verified in system tests. - log.info("Done serving reference '" + reference.toString() + "' with file '" + file.getAbsolutePath() + "'"); + + target.receive(fileData, new ReplayStatus(success ? 0 : 1, success ? "OK" : errorDescription)); + log.log(LogLevel.DEBUG, "Done serving reference '" + reference.toString() + "' with file '" + file.getAbsolutePath() + "'"); + } + + private FileReferenceData readFileReferenceData(FileReference reference) throws IOException { + File file = root.getFile(reference); + + if (file.isDirectory()) { + //TODO Here we should compress to file, but then we have to clean up too. Pending. + byte [] blob = CompressedFileReference.compress(file.getParentFile()); + return new FileReferenceDataBlob(reference, file.getName(), FileReferenceData.Type.compressed, blob); + } else { + return new LazyFileReferenceData(reference, file.getName(), FileReferenceData.Type.file, file); + } + } + public void serveFile(Request request, Receiver receiver) { + pullExecutor.execute(() -> serveFile(request.parameters().get(0).asString(), request, receiver)); + } + private void serveFile(String fileReference, Request request, Receiver receiver) { + FileApiErrorCodes result; + try { + log.log(LogLevel.DEBUG, "Received request for reference '" + fileReference + "'"); + result = hasFile(fileReference) + ? FileApiErrorCodes.OK + : FileApiErrorCodes.NOT_FOUND; + if (result == FileApiErrorCodes.OK) { + startFileServing(fileReference, receiver); + } else { + download(new FileReference(fileReference)); + } + } catch (IllegalArgumentException e) { + result = FileApiErrorCodes.NOT_FOUND; + log.warning("Failed serving file reference '" + fileReference + "' with error " + e.toString()); + } + request.returnValues() + .add(new Int32Value(result.getCode())) + .add(new StringValue(result.getDescription())); + request.returnRequest(); } public void download(FileReference fileReference) { downloader.getFile(fileReference); } + + public FileDownloader downloader() { + return downloader; + } + + // Connection pool with all config servers except this one (might be an empty pool if there is only one config server) + private static ConnectionPool createConnectionPool(ConfigserverConfig configserverConfig) { + List<String> configServers = ConfigServerSpec.fromConfig(configserverConfig) + .stream() + .filter(spec -> !spec.getHostName().equals(HostName.getLocalhost())) + .map(spec -> "tcp/" + spec.getHostName() + ":" + spec.getConfigServerPort()) + .collect(Collectors.toList()); + + return configServers.size() > 0 ? new JRTConnectionPool(new ConfigSourceSet(configServers)) : new EmptyConnectionPool(); + } + + private static class EmptyConnectionPool implements ConnectionPool { + + @Override + public void close() {} + + @Override + public void setError(Connection connection, int i) {} + + @Override + public Connection getCurrent() { return null; } + + @Override + public Connection setNewCurrentConnection() { return null; } + + @Override + public int getSize() { return 0; } + + @Override + public Supervisor getSupervisor() { return new Supervisor(new Transport()); } + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java index eb23d38e23e..caf64cca4d0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java @@ -8,8 +8,7 @@ import java.util.Collection; import java.util.Set; /** - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ public class MockFileDBHandler implements FileDistribution { public int sendDeployedFilesCalled = 0; @@ -22,6 +21,9 @@ public class MockFileDBHandler implements FileDistribution { } @Override + public void startDownload(String hostName, Set<FileReference> fileReferences) { /* not implemented */ } + + @Override public void reloadDeployFileDistributor() { reloadDeployFileDistributorCalled++; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java index 48732814919..ea8405f6b65 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java @@ -185,7 +185,8 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { HostName.from(configserverConfig.loadBalancerAddress()), configserverConfig.hostedVespa(), zone, - rotations); + rotations, + configserverConfig.disableFiledistributor()); } /** diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java index 78d660b347e..b3ade24603c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java @@ -88,7 +88,8 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P log.log(LogLevel.DEBUG, "Building model " + modelVersion + " for " + applicationId); FileDistributionProvider fileDistributionProvider = fileDistributionFactory.createProvider( context.getServerDBSessionDir(), - applicationId); + applicationId, + properties.disableFileDistributor()); // Use empty on non-hosted systems, use already allocated hosts if available, create connection to a host provisioner otherwise Optional<HostProvisioner> hostProvisioner = createHostProvisioner(allocatedHosts); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java index d17cdf722ea..72576292471 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java @@ -41,8 +41,8 @@ import com.yahoo.vespa.config.server.monitoring.MetricUpdaterFactory; import com.yahoo.vespa.config.server.tenant.TenantHandlerProvider; import com.yahoo.vespa.config.server.tenant.TenantListener; import com.yahoo.vespa.config.server.tenant.Tenants; -import net.jpountz.xxhash.XXHash64; -import net.jpountz.xxhash.XXHashFactory; +import com.yahoo.vespa.filedistribution.FileReceiver; +import com.yahoo.vespa.filedistribution.FileReferenceData; import java.nio.ByteBuffer; import java.util.Collection; @@ -76,20 +76,9 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { static final int TRACELEVEL_DEBUG = 9; private static final String THREADPOOL_NAME = "rpcserver worker pool"; private static final long SHUTDOWN_TIMEOUT = 60; - private enum FileApiErrorCodes { - OK(0, "OK"), - NOT_FOUND(1, "Filereference not found"); - private final int code; - private final String description; - FileApiErrorCodes(int code, String description) { - this.code = code; - this.description = description; - } - int getCode() { return code; } - String getDescription() { return description; } - } + private final Supervisor supervisor = new Supervisor(new Transport()); - private Spec spec = null; + private Spec spec; private final boolean useRequestVersion; private final boolean hostedVespa; @@ -106,6 +95,7 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { private final FileServer fileServer; private final ThreadPoolExecutor executorService; + private final boolean useChunkedFileTransfer; private volatile boolean allTenantsLoaded = false; /** @@ -131,6 +121,7 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { this.useRequestVersion = config.useVespaVersionInRequest(); this.hostedVespa = config.hostedVespa(); this.fileServer = fileServer; + this.useChunkedFileTransfer = config.usechunkedtransfer(); setUpHandlers(); } @@ -426,9 +417,9 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { return useRequestVersion; } - class FileReceiver implements FileServer.Receiver { + class WholeFileReceiver implements FileServer.Receiver { Target target; - FileReceiver(Target target) { + WholeFileReceiver(Target target) { this.target = target; } @@ -438,44 +429,111 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { } @Override - public void receive(FileReference reference, String filename, byte [] content, FileServer.ReplayStatus status) { - XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); - Request fileBlob = new Request("filedistribution.receiveFile"); - fileBlob.parameters().add(new StringValue(reference.value())); - fileBlob.parameters().add(new StringValue(filename)); - fileBlob.parameters().add(new DataValue(content)); - fileBlob.parameters().add(new Int64Value(hasher.hash(ByteBuffer.wrap(content), 0))); + public void receive(FileReferenceData fileData, FileServer.ReplayStatus status) { + Request fileBlob = new Request(FileReceiver.RECEIVE_METHOD); + fileBlob.parameters().add(new StringValue(fileData.fileReference().value())); + fileBlob.parameters().add(new StringValue(fileData.filename())); + fileBlob.parameters().add(new StringValue(fileData.type().name())); + fileBlob.parameters().add(new DataValue(fileData.content().array())); + fileBlob.parameters().add(new Int64Value(fileData.xxhash())); fileBlob.parameters().add(new Int32Value(status.getCode())); fileBlob.parameters().add(new StringValue(status.getDescription())); target.invokeSync(fileBlob, 600); if (fileBlob.isError()) { - log.warning("Failed delivering reference '" + reference.value() + "' with file '" + filename + "' to " + - target.toString() + " with error : '" + fileBlob.errorMessage() + "'."); + log.warning("Failed delivering reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " + + target.toString() + " with error: '" + fileBlob.errorMessage() + "'."); } } } - @SuppressWarnings("UnusedDeclaration") - public final void serveFile(Request request) { - String fileReference = request.parameters().get(0).asString(); - FileApiErrorCodes result; - try { - // TODO remove once verified in system tests. - log.info("Received request for reference '" + fileReference + "'"); - result = fileServer.hasFile(fileReference) - ? FileApiErrorCodes.OK - : FileApiErrorCodes.NOT_FOUND; - if (result == FileApiErrorCodes.OK) { - fileServer.startFileServing(fileReference, new FileReceiver(request.target())); - } else { - fileServer.download(new FileReference(fileReference)); + class ChunkedFileReceiver implements FileServer.Receiver { + Target target; + ChunkedFileReceiver(Target target) { + this.target = target; + } + + @Override + public String toString() { + return target.toString(); + } + + @Override + public void receive(FileReferenceData fileData, FileServer.ReplayStatus status) { + int session = sendMeta(fileData); + sendParts(session, fileData); + sendEof(session, fileData, status); + } + private void sendParts(int session, FileReferenceData fileData) { + ByteBuffer bb = ByteBuffer.allocate(0x100000); + for (int partId = 0, read = fileData.nextContent(bb); read >= 0; partId++, read = fileData.nextContent(bb)) { + byte [] buf = bb.array(); + if (buf.length != bb.position()) { + buf = new byte [bb.position()]; + bb.flip(); + bb.get(buf); + } + sendPart(session, fileData.fileReference(), partId, buf); + bb.clear(); + } + } + private int sendMeta(FileReferenceData fileData) { + Request request = new Request(FileReceiver.RECEIVE_META_METHOD); + request.parameters().add(new StringValue(fileData.fileReference().value())); + request.parameters().add(new StringValue(fileData.filename())); + request.parameters().add(new StringValue(fileData.type().name())); + request.parameters().add(new Int64Value(fileData.size())); + target.invokeSync(request, 600); + if (request.isError()) { + log.warning("Failed delivering meta for reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " + + target.toString() + " with error: '" + request.errorMessage() + "'."); + } + int retCode = request.returnValues().get(0).asInt32(); + if (retCode != 0) { + throw new IllegalArgumentException("Unknown error from target '" + target.toString() + "' during rpc call " + request.methodName()); + } + return request.returnValues().get(1).asInt32(); + } + private void sendPart(int session, FileReference ref, int partId, byte [] buf) { + Request request = new Request(FileReceiver.RECEIVE_PART_METHOD); + request.parameters().add(new StringValue(ref.value())); + request.parameters().add(new Int32Value(session)); + request.parameters().add(new Int32Value(partId)); + request.parameters().add(new DataValue(buf)); + target.invokeSync(request, 600); + if (request.isError()) { + log.warning("Failed delivering reference '" + ref.value() + "' to " + + target.toString() + " with error: '" + request.errorMessage() + "'."); + } + int retCode = request.returnValues().get(0).asInt32(); + if (retCode != 0) { + throw new IllegalArgumentException("Unknown error from target '" + target.toString() + "' during rpc call " + request.methodName()); + } + } + private void sendEof(int session, FileReferenceData fileData, FileServer.ReplayStatus status) { + Request request = new Request(FileReceiver.RECEIVE_EOF_METHOD); + request.parameters().add(new StringValue(fileData.fileReference().value())); + request.parameters().add(new Int32Value(session)); + request.parameters().add(new Int64Value(fileData.xxhash())); + request.parameters().add(new Int32Value(status.getCode())); + request.parameters().add(new StringValue(status.getDescription())); + target.invokeSync(request, 600); + if (request.isError()) { + log.warning("Failed delivering reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " + + target.toString() + " with error: '" + request.errorMessage() + "'."); + } + int retCode = request.returnValues().get(0).asInt32(); + if (retCode != 0) { + throw new IllegalArgumentException("Unknown error from target '" + target.toString() + "' during rpc call " + request.methodName()); } - } catch (IllegalArgumentException e) { - result = FileApiErrorCodes.NOT_FOUND; - log.warning("Failed serving file reference '" + fileReference + "' with error " + e.toString()); } - request.returnValues() - .add(new Int32Value(result.getCode())) - .add(new StringValue(result.getDescription())); + } + + @SuppressWarnings("UnusedDeclaration") + public final void serveFile(Request request) { + request.detach(); + FileServer.Receiver receiver = useChunkedFileTransfer + ? new ChunkedFileReceiver(request.target()) + : new WholeFileReceiver(request.target()); + fileServer.serveFile(request, receiver); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java index 003f931a218..99a34a45a2f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/FileDistributionFactory.java @@ -13,8 +13,7 @@ import java.util.concurrent.locks.Lock; /** * Factory for creating providers that are used to interact with file distribution. * - * @author lulf - * @since 5.1 + * @author Ulf Lilleengen */ @SuppressWarnings("WeakerAccess") public class FileDistributionFactory { @@ -33,8 +32,8 @@ public class FileDistributionFactory { this.zkSpec = zkSpec; } - public FileDistributionProvider createProvider(File applicationPackage, ApplicationId applicationId) { - return new FileDistributionProvider(applicationPackage, zkSpec, applicationId.serializedForm(), lock); + public FileDistributionProvider createProvider(File applicationPackage, ApplicationId applicationId, boolean disableFileDistributor) { + return new FileDistributionProvider(applicationPackage, zkSpec, applicationId.serializedForm(), lock, disableFileDistributor); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java index 2269a7ed997..41c4ddd676e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java @@ -28,12 +28,11 @@ import org.apache.curator.framework.recipes.cache.*; /** * Will watch/prepare sessions (applications) based on watched nodes in ZooKeeper, set for example * by the prepare HTTP handler on another configserver. The zookeeper state watched in this class is shared - * between all configservers, so it should not modify any global state, because the operation will be performed + * between all config servers, so it should not modify any global state, because the operation will be performed * on all servers. The repo can be regarded as read only from the POV of the configserver. * - * @author vegardh - * @author lulf - * @since 5.1 + * @author Vegard Havdal + * @author Ulf Lilleengen */ public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements NodeCacheListener, PathChildrenCacheListener { @@ -74,6 +73,17 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements Nod sessionsChanged(); } + // For testing only + public RemoteSessionRepo(TenantName tenantName) { + this.curator = null; + this.remoteSessionFactory = null; + this.reloadHandler = null; + this.sessionsPath = Tenants.getSessionsPath(tenantName); + this.metrics = null; + this.directoryCache = null; + this.applicationRepo = null; + } + //---------- START overrides to keep sessions changed in sync @Override @@ -122,17 +132,6 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements Nod } } - // For testing only - public RemoteSessionRepo() { - this.curator = null; - this.remoteSessionFactory = null; - this.reloadHandler = null; - this.sessionsPath = Path.createRoot(); - this.metrics = null; - this.directoryCache = null; - this.applicationRepo = null; - } - private List<Long> getSessionList(List<ChildData> children) { List<Long> sessions = new ArrayList<>(); for (ChildData data : children) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index 531085883c4..24666f42dc9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -155,7 +155,8 @@ public class SessionPreparer { HostName.from(configserverConfig.loadBalancerAddress()), configserverConfig.hostedVespa(), zone, - rotationsSet); + rotationsSet, + configserverConfig.disableFiledistributor()); this.preparedModelsBuilder = new PreparedModelsBuilder(modelFactoryRegistry, permanentApplicationPackage, configDefinitionRepo, @@ -177,7 +178,7 @@ public class SessionPreparer { void preprocess() { try { - this.applicationPackage = context.getApplicationPackage().preprocess(properties.zone(), null, logger); + this.applicationPackage = context.getApplicationPackage().preprocess(properties.zone().id(), null, logger); } catch (IOException | TransformerException | ParserConfigurationException | SAXException e) { throw new RuntimeException("Error deploying application package", e); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java index d2cf17a38d4..5fb62a68e3e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java @@ -96,18 +96,17 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen } /** - * New instance containing the given tenants. This will not create Zookeeper watches. For testing only + * New instance containing the given tenants. Creates no system tenants and noZookeeper watches. For testing only. * @param globalComponentRegistry a {@link com.yahoo.vespa.config.server.GlobalComponentRegistry} instance * @param tenants a collection of {@link Tenant}s */ + // TODO: Get rid of the second argument and let callers use addTenant() instead public Tenants(GlobalComponentRegistry globalComponentRegistry, Collection<Tenant> tenants) { this.globalComponentRegistry = globalComponentRegistry; this.curator = globalComponentRegistry.getCurator(); metricUpdater = globalComponentRegistry.getMetrics().getOrCreateMetricUpdater(Collections.emptyMap()); this.tenantListeners.add(globalComponentRegistry.getTenantListener()); curator.create(tenantsPath); - createSystemTenants(globalComponentRegistry.getConfigserverConfig()); - createTenants(); this.directoryCache = curator.createDirectoryCache(tenantsPath.getAbsolute(), false, false, pathChildrenExecutor); this.tenants.putAll(addTenants(tenants)); } @@ -120,13 +119,13 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen // Pre-condition: tenants path needs to exist in zk private LinkedHashMap<TenantName, Tenant> addTenants(Collection<Tenant> newTenants) { - LinkedHashMap<TenantName, Tenant> sessionTenants = new LinkedHashMap<>(); + LinkedHashMap<TenantName, Tenant> tenants = new LinkedHashMap<>(); for (Tenant t : newTenants) { - sessionTenants.put(t.getName(), t); + tenants.put(t.getName(), t); } - log.log(LogLevel.DEBUG, "Tenants at startup: " + sessionTenants); - metricUpdater.setTenants(tenants.size()); - return sessionTenants; + log.log(LogLevel.DEBUG, "Tenants at startup: " + tenants); + metricUpdater.setTenants(this.tenants.size()); + return tenants; } public synchronized void addTenant(TenantName tenantName) throws Exception { @@ -191,7 +190,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen try { Tenant tenant = TenantBuilder.create(globalComponentRegistry, tenantName).build(); notifyNewTenant(tenant); - tenants.put(tenantName, tenant); + tenants.putIfAbsent(tenantName, tenant); } catch (Exception e) { log.log(LogLevel.WARNING, "Error loading tenant '" + tenantName + "', skipping.", e); } diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml index 635ce07e727..fd77fedd789 100644 --- a/configserver/src/main/resources/configserver-app/services.xml +++ b/configserver/src/main/resources/configserver-app/services.xml @@ -6,6 +6,10 @@ <maxthreads>100</maxthreads> <!-- Reduced thread count to minimize memory consumption --> </config> + <config name="container.jdisc.config.health-monitor"> + <initialStatus>initializing</initialStatus> + </config> + <accesslog type="vespa" fileNamePattern="logs/vespa/configserver/access.log.%Y%m%d%H%M%S" rotationScheme="date" symlinkName="access.log" /> <preprocess:include file='access-logging.xml' required='false' /> <component id="com.yahoo.vespa.config.server.ConfigServerBootstrap" bundle="configserver" /> @@ -34,6 +38,7 @@ <component id="com.yahoo.config.provision.Zone" bundle="config-provisioning" /> <component id="com.yahoo.vespa.config.server.application.ApplicationConvergenceChecker" bundle="configserver" /> <component id="com.yahoo.vespa.config.server.application.HttpProxy" bundle="configserver" /> + <component id="com.yahoo.vespa.config.server.filedistribution.FileServer" bundle="configserver" /> <component id="com.yahoo.vespa.serviceview.ConfigServerLocation" bundle="configserver" /> @@ -72,64 +77,96 @@ <handler id='com.yahoo.vespa.config.server.http.HttpGetConfigHandler' bundle='configserver'> <binding>http://*/config/v1/*/*</binding> + <binding>https://*/config/v1/*/*</binding> <binding>http://*/config/v1/*</binding> + <binding>https://*/config/v1/*</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.HttpListConfigsHandler' bundle='configserver'> <binding>http://*/config/v1/</binding> + <binding>https://*/config/v1/</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.HttpListNamedConfigsHandler' bundle='configserver'> <binding>http://*/config/v1/*/</binding> + <binding>https://*/config/v1/*/</binding> <binding>http://*/config/v1/*/*/</binding> + <binding>https://*/config/v1/*/*/</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.TenantHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/</binding> + <binding>https://*/application/v2/tenant/</binding> <binding>http://*/application/v2/tenant/*</binding> + <binding>https://*/application/v2/tenant/*</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.SessionCreateHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/*/session</binding> + <binding>https://*/application/v2/tenant/*/session</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.SessionPrepareHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/*/session/*/prepared</binding> + <binding>https://*/application/v2/tenant/*/session/*/prepared</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.SessionActiveHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/*/session/*/active</binding> + <binding>https://*/application/v2/tenant/*/session/*/active</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.SessionContentHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/*/session/*/content/*</binding> + <binding>https://*/application/v2/tenant/*/session/*/content/*</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.ListApplicationsHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/*/application/</binding> + <binding>https://*/application/v2/tenant/*/application/</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.ApplicationHandler' bundle='configserver'> <!-- WARNING: THIS LIST *MUST* MATCH THE ONE IN ApplicationHandler::getBindingMatch --> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/content/*</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/content/*</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/restart</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/restart</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/log</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/log</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/converge</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/converge</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*</binding> + <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*</binding> <binding>http://*/application/v2/tenant/*/application/*</binding> + <binding>https://*/application/v2/tenant/*/application/*</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.HttpGetConfigHandler' bundle='configserver'> <binding>http://*/config/v2/tenant/*/application/*/*</binding> + <binding>https://*/config/v2/tenant/*/application/*/*</binding> <binding>http://*/config/v2/tenant/*/application/*/*/*</binding> + <binding>https://*/config/v2/tenant/*/application/*/*/*</binding> <binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*</binding> + <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*</binding> <binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*</binding> + <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.HttpListConfigsHandler' bundle='configserver'> <binding>http://*/config/v2/tenant/*/application/*/</binding> + <binding>https://*/config/v2/tenant/*/application/*/</binding> <binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/</binding> + <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.HttpListNamedConfigsHandler' bundle='configserver'> <binding>http://*/config/v2/tenant/*/application/*/*/</binding> + <binding>https://*/config/v2/tenant/*/application/*/*/</binding> <binding>http://*/config/v2/tenant/*/application/*/*/*/</binding> + <binding>https://*/config/v2/tenant/*/application/*/*/*/</binding> <binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/</binding> + <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/</binding> <binding>http://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*/</binding> + <binding>https://*/config/v2/tenant/*/application/*/environment/*/region/*/instance/*/*/*/</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.HostHandler' bundle='configserver'> - <binding>http://*/application/v2/host/*</binding> + <binding>http://*/application/v2/host/*</binding> + <binding>https://*/application/v2/host/*</binding> </handler> <http> diff --git a/configserver/src/main/sh/start-filedistribution b/configserver/src/main/sh/start-filedistribution index bb8599f2bc9..1995e59de4e 100755 --- a/configserver/src/main/sh/start-filedistribution +++ b/configserver/src/main/sh/start-filedistribution @@ -63,7 +63,7 @@ ROOT=${VESPA_HOME%/} VESPA_CONFIG_ID="dir:${ROOT}/conf/filedistributor" export VESPA_CONFIG_ID -if [ "$multitenant" = "true" ]; then +if [ "$multitenant" = "true" ] && [ "$disable_filedistributor" = "" ] || [ "$disable_filedistributor" = "false" ]; then foo=`${ROOT}/libexec/vespa/vespa-config.pl -mkfiledistributorconfig` PIDFILE_FILEDISTRIBUTOR=var/run/filedistributor.pid LOGFILE="${ROOT}/logs/vespa/vespa.log" diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java index e0d65055f21..5d573323bb6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java @@ -6,7 +6,10 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.TenantName; +import com.yahoo.container.jdisc.config.HealthMonitorConfig; +import com.yahoo.container.jdisc.state.StateMonitor; import com.yahoo.io.IOUtils; +import com.yahoo.jdisc.core.SystemTimer; import com.yahoo.vespa.config.server.deploy.MockDeployer; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.http.SessionHandlerTest; @@ -26,7 +29,6 @@ import java.io.File; import java.io.FileReader; import java.time.Clock; import java.util.ArrayList; -import java.util.Optional; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertFalse; @@ -67,7 +69,9 @@ public class ConfigServerBootstrapTest extends TestWithTenant { assertFalse(myServer.stopped); VersionState versionState = new VersionState(versionFile); assertTrue(versionState.isUpgraded()); - ConfigServerBootstrap bootstrap = new ConfigServerBootstrap(applicationRepository, rpc, (application, timeout) -> Optional.empty(), versionState); + ConfigServerBootstrap bootstrap = + new ConfigServerBootstrap(applicationRepository, rpc, new MockDeployer(), versionState, + new StateMonitor(new HealthMonitorConfig(new HealthMonitorConfig.Builder()), new SystemTimer())); waitUntilStarted(rpc, 60000); assertFalse(versionState.isUpgraded()); assertThat(versionState.currentVersion(), is(versionState.storedVersion())); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java index aed0a6a9750..e4c336a55f1 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java @@ -48,7 +48,8 @@ public class ModelContextImplTest { null, false, Zone.defaultZone(), - rotations), + rotations, + false), Optional.empty(), new Version(6), new Version(6)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java index d9a0db7e811..63f0f5f26b7 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -23,6 +23,7 @@ import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ProvisionLogger; import com.yahoo.config.provision.Provisioner; +import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.config.server.ApplicationRepository; @@ -57,6 +58,8 @@ import java.util.Optional; */ public class DeployTester { + private static final TenantName tenantName = TenantName.from("deploytester"); + private final Clock clock; private final Tenants tenants; private final File testApp; @@ -95,6 +98,7 @@ public class DeployTester { try { this.testApp = new File(appPath); this.tenants = new Tenants(componentRegistry, Collections.emptySet()); + tenants.addTenant(tenantName); } catch (Exception e) { throw new IllegalArgumentException(e); @@ -103,7 +107,7 @@ public class DeployTester { } public Tenant tenant() { - return tenants.defaultTenant(); + return tenants.getTenant(tenantName); } /** Create a model factory for the version of this source*/ diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java index 4fc54943a79..051e7c9a8f9 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java @@ -15,6 +15,11 @@ public class MockDeployer implements com.yahoo.config.provision.Deployer { public ApplicationId lastDeployed; @Override + public Optional<Deployment> deployFromLocalActive(ApplicationId application) { + return deployFromLocalActive(application, Duration.ofSeconds(60)); + } + + @Override public Optional<Deployment> deployFromLocalActive(ApplicationId application, Duration timeout) { lastDeployed = application; return Optional.empty(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java index 76734aadae7..50e8c711330 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java @@ -7,7 +7,6 @@ import com.yahoo.config.model.api.ModelFactory; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.InstanceName; -import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.test.ManualClock; import com.yahoo.vespa.config.server.session.LocalSession; @@ -60,7 +59,7 @@ public class RedeployTest { modelFactories.add(DeployTester.createModelFactory(Clock.systemUTC())); modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0))); DeployTester tester = new DeployTester("ignored/app/path", modelFactories); - ApplicationId id = ApplicationId.from(TenantName.from("default"), + ApplicationId id = ApplicationId.from(tester.tenant().getName(), ApplicationName.from("default"), InstanceName.from("default")); assertFalse(tester.redeployFromLocalActive(id).isPresent()); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java new file mode 100644 index 00000000000..ad807f9527f --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDirectoryTest.java @@ -0,0 +1,69 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.config.FileReference; +import com.yahoo.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertTrue; + +public class FileDirectoryTest { + + private FileDirectory fileDirectory; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void setup() { + fileDirectory = new FileDirectory(temporaryFolder.getRoot()); + } + + @Test + public void requireThatFileReferenceWithFilesWorks() throws IOException { + FileReference foo = createFile("foo"); + FileReference bar = createFile("bar"); + + assertTrue(fileDirectory.getFile(foo).exists()); + assertTrue(fileDirectory.getFile(bar).exists()); + } + + + @Test + public void requireThatFileReferenceWithSubDirectoriesWorks() throws IOException { + FileDirectory fileDirectory = new FileDirectory(temporaryFolder.getRoot()); + + FileReference foo = createFileInSubDir("subdir", "foo"); + FileReference bar = createFileInSubDir("subdir", "bar"); + + assertTrue(fileDirectory.getFile(foo).exists()); + assertTrue(fileDirectory.getFile(bar).exists()); + } + + // Content in created file is equal to the filename string + private FileReference createFile(String filename) throws IOException { + File file = temporaryFolder.newFile(filename); + IOUtils.writeFile(file, filename, false); + return fileDirectory.addFile(file); + } + + private FileReference createFileInSubDir(String subdirName, String filename) throws IOException { + File subDirectory = new File(temporaryFolder.getRoot(), subdirName); + if (!subDirectory.exists()) + subDirectory.mkdirs(); + File file = new File(subDirectory, filename); + IOUtils.writeFile(file, filename, false); + return fileDirectory.addFile(file); + } + + +} + + diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java index 4913798e5ad..b0dce359d58 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java @@ -1,12 +1,15 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.filedistribution; -import com.yahoo.config.FileReference; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.io.IOUtils; +import com.yahoo.net.HostName; +import com.yahoo.vespa.filedistribution.FileReferenceData; import org.junit.Test; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -32,7 +35,7 @@ public class FileServerTest { } @Test - public void requireThatExistingFileCanbeFound() throws IOException { + public void requireThatExistingFileCanBeFound() throws IOException { createCleanDir("123"); IOUtils.writeFile("123/f1", "test", true); assertTrue(fs.hasFile("123")); @@ -47,16 +50,15 @@ public class FileServerTest { cleanup(); } - private static class FileReceiver implements FileServer.Receiver { - CompletableFuture<byte []> content; - FileReceiver(CompletableFuture<byte []> content) { - this.content = content; - } - @Override - public void receive(FileReference reference, String filename, byte[] content, FileServer.ReplayStatus status) { - this.content.complete(content); - } + @Test + public void requireThatFileReferenceWithDirectoryCanBeFound() throws IOException { + createCleanDir("124/subdir"); + IOUtils.writeFile("124/subdir/f1", "test", false); + IOUtils.writeFile("124/subdir/f2", "test", false); + assertTrue(fs.hasFile("124/subdir")); + cleanup(); } + @Test public void requireThatWeCanReplayFile() throws IOException, InterruptedException, ExecutionException { createCleanDir("12y"); @@ -67,6 +69,44 @@ public class FileServerTest { cleanup(); } + @Test + public void requireThatDifferentNumberOfConfigServersWork() throws IOException { + // Empty connection pool in tests etc. + ConfigserverConfig.Builder builder = new ConfigserverConfig.Builder(); + FileServer fileServer = new FileServer(new ConfigserverConfig(builder)); + assertEquals(0, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize()); + + // Empty connection pool when only one server, no use in downloading from yourself + List<ConfigserverConfig.Zookeeperserver.Builder> servers = new ArrayList<>(); + ConfigserverConfig.Zookeeperserver.Builder serverBuilder = new ConfigserverConfig.Zookeeperserver.Builder(); + serverBuilder.hostname(HostName.getLocalhost()); + serverBuilder.port(123456); + servers.add(serverBuilder); + builder.zookeeperserver(servers); + fileServer = new FileServer(new ConfigserverConfig(builder)); + assertEquals(0, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize()); + + // connection pool of size 1 when 2 servers + ConfigserverConfig.Zookeeperserver.Builder serverBuilder2 = new ConfigserverConfig.Zookeeperserver.Builder(); + serverBuilder2.hostname("bar"); + serverBuilder2.port(123456); + servers.add(serverBuilder2); + builder.zookeeperserver(servers); + fileServer = new FileServer(new ConfigserverConfig(builder)); + assertEquals(1, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize()); + } + + private static class FileReceiver implements FileServer.Receiver { + CompletableFuture<byte []> content; + FileReceiver(CompletableFuture<byte []> content) { + this.content = content; + } + @Override + public void receive(FileReferenceData fileData, FileServer.ReplayStatus status) { + this.content.complete(fileData.content().array()); + } + } + private void cleanup() { created.forEach((file) -> IOUtils.recursiveDeleteDir(file)); created.clear(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 9d04b7e982d..6542c865d56 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -84,16 +84,24 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { private LocalSessionRepo localRepo; private TenantApplications applicationRepo; private MockProvisioner hostProvisioner; + private VespaModelFactory modelFactory; + private TestComponentRegistry componentRegistry; @Before public void setup() throws Exception { - remoteSessionRepo = new RemoteSessionRepo(); + remoteSessionRepo = new RemoteSessionRepo(tenant); applicationRepo = new MemoryTenantApplications(); curator = new MockCurator(); configCurator = ConfigCurator.create(curator); localRepo = new LocalSessionRepo(Clock.systemUTC()); pathPrefix = "/application/v2/tenant/" + tenant + "/session/"; hostProvisioner = new MockProvisioner(); + modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); + componentRegistry = new TestComponentRegistry.Builder() + .curator(curator) + .configCurator(configCurator) + .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(modelFactory))) + .build(); } @Test @@ -211,14 +219,8 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient, Clock clock) throws IOException { zkClient.writeStatus(status); ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, Tenants.getSessionsPath(tenant).append(String.valueOf(sessionId))); - VespaModelFactory modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); zkC.write(Collections.singletonMap(modelFactory.getVersion(), new MockFileRegistry())); zkC.write(AllocatedHosts.withHosts(Collections.emptySet())); - TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() - .curator(curator) - .configCurator(configCurator) - .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(modelFactory))) - .build(); RemoteSession session = new RemoteSession(TenantName.from("default"), sessionId, componentRegistry, zkClient, clock); remoteSessionRepo.addSession(session); return session; @@ -246,7 +248,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { return activateRequest; } - private ActivateRequest activateAndAssertErrorPut(long sessionId, long previousSessionId, Clock clock, + private void activateAndAssertErrorPut(long sessionId, long previousSessionId, Clock clock, int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, "", clock); activateRequest.invoke(); @@ -256,7 +258,6 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { String message = getRenderedString(actResponse); assertThat(message, Is.is("{\"error-code\":\"" + errorCode.name() + "\",\"message\":\"" + expectedError + "\"}")); assertThat(session.getStatus(), Is.is(Session.Status.PREPARE)); - return activateRequest; } private void testUnsupportedMethod(com.yahoo.container.jdisc.HttpRequest request) throws Exception { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java index 310342e81f1..65b12490b17 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java @@ -45,7 +45,6 @@ import static org.junit.Assert.fail; /** * @author hmusum - * @since 5.1 */ public class SessionCreateHandlerTest extends SessionHandlerTest { @@ -106,7 +105,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { assertThat(factory.applicationName, is("ulfio")); } - protected void assertFromParameter(String expected, String from) throws IOException { + private void assertFromParameter(String expected, String from) throws IOException { HttpRequest request = post(Collections.singletonMap("from", from)); MockSessionFactory factory = new MockSessionFactory(); factory.applicationPackage = testApp; @@ -120,7 +119,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { expected + "/content/\",\"message\":\"Session " + expected + createdMessage + "}")); } - protected void assertIllegalFromParameter(String fromValue) throws IOException { + private void assertIllegalFromParameter(String fromValue) throws IOException { File outFile = CompressedApplicationInputStreamTest.createTarFile(); HttpRequest request = post(outFile, postHeaders, Collections.singletonMap("from", fromValue)); HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(createHandler().handle(request), BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' has illegal value '" + fromValue + "'"); @@ -218,7 +217,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod/region/baz/instance"); } - public SessionCreateHandler createHandler() { + private SessionCreateHandler createHandler() { try { return createHandler(new MockSessionFactory()); } catch (Exception e) { @@ -228,7 +227,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { return null; } - public SessionCreateHandler createHandler(SessionFactory sessionFactory) { + private SessionCreateHandler createHandler(SessionFactory sessionFactory) { try { TestTenantBuilder testBuilder = new TestTenantBuilder(); testBuilder.createTenant(tenant).withSessionFactory(sessionFactory) @@ -250,15 +249,15 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { Clock.systemUTC())); } - public HttpRequest post() throws FileNotFoundException { + private HttpRequest post() throws FileNotFoundException { return post(null, postHeaders, new HashMap<>()); } - public HttpRequest post(File file) throws FileNotFoundException { + private HttpRequest post(File file) throws FileNotFoundException { return post(file, postHeaders, new HashMap<>()); } - public HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException { + private HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException { HttpRequest request = HttpRequest.createTestRequest("http://" + hostname + ":" + port + "/application/v2/tenant/" + tenant + "/session", POST, file == null ? null : new FileInputStream(file), @@ -269,7 +268,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { return request; } - public HttpRequest post(Map<String, String> parameters) throws FileNotFoundException { + private HttpRequest post(Map<String, String> parameters) throws FileNotFoundException { return post(null, new HashMap<>(), parameters); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java index 7900a67bddd..74a2dcf8054 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java @@ -32,7 +32,6 @@ import com.yahoo.vespa.config.server.http.*; import com.yahoo.vespa.config.server.session.*; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; -import org.apache.commons.lang.NullArgumentException; import org.junit.Before; import org.junit.Test; @@ -148,7 +147,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { * A mock remote session repo based on contents of local repo */ private RemoteSessionRepo fromLocalSessionRepo(LocalSessionRepo localRepo, Clock clock) { - RemoteSessionRepo remoteRepo = new RemoteSessionRepo(); + RemoteSessionRepo remoteRepo = new RemoteSessionRepo(tenant); for (LocalSession ls : localRepo.listSessions()) { zooKeeperClient = new MockSessionZKClient(curator, tenant, ls.getSessionId()); @@ -239,7 +238,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { // Need different repos for 'default' tenant as opposed to the 'test' tenant LocalSessionRepo localRepoDefault = new LocalSessionRepo(Clock.systemUTC()); final TenantName defaultTenant = TenantName.defaultName(); - addTenant(defaultTenant, localRepoDefault, new RemoteSessionRepo(), new MockSessionFactory()); + addTenant(defaultTenant, localRepoDefault, new RemoteSessionRepo(tenant), new MockSessionFactory()); addTestTenant(); final SessionHandler handler = createHandler(builder); @@ -379,7 +378,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } private TestTenantBuilder addTestTenant() { - return addTenant(tenant, localRepo, new RemoteSessionRepo(), new MockSessionFactory()); + return addTenant(tenant, localRepo, new RemoteSessionRepo(tenant), new MockSessionFactory()); } private SessionHandler createHandler(TestTenantBuilder builder) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java index 16ce605d4d1..32f29858b74 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java @@ -19,7 +19,6 @@ import java.util.*; * Test utility for creating tenants used for testing and setup wiring of tenant stuff. * * @author Ulf Lilleengen - * @since 5.1 */ public class TestTenantBuilder { @@ -35,7 +34,7 @@ public class TestTenantBuilder { TenantBuilder builder = TenantBuilder.create(componentRegistry, tenantName) .withSessionFactory(new SessionCreateHandlerTest.MockSessionFactory()) .withLocalSessionRepo(new LocalSessionRepo(componentRegistry.getClock())) - .withRemoteSessionRepo(new RemoteSessionRepo()) + .withRemoteSessionRepo(new RemoteSessionRepo(tenantName)) .withApplicationRepo(applicationRepo); tenantMap.put(tenantName, builder); return builder; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java index ce9de5e066f..d32b3a9e1a6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java @@ -10,8 +10,7 @@ import com.yahoo.vespa.curator.mock.MockCurator; import java.io.File; /** -* @author lulf -* @since 5.1 +* @author Ulf Lilleengen */ public class MockFileDistributionFactory extends FileDistributionFactory { @@ -27,7 +26,7 @@ public class MockFileDistributionFactory extends FileDistributionFactory { } @Override - public FileDistributionProvider createProvider(File applicationFile, ApplicationId applicationId) { + public FileDistributionProvider createProvider(File applicationFile, ApplicationId applicationId, boolean disableFileDistributor) { return mockFileDistributionProvider; } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java index e650997b7e0..617ad8733f9 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java @@ -125,13 +125,10 @@ public class TenantsTestCase extends TestWithCurator { public void testTenantsChanged() throws Exception { tenants.close(); // close the Tenants instance created in setupSession, we do not want to use one with a PatchChildrenCache listener tenants = new Tenants(globalComponentRegistry, new ArrayList<>()); - TenantName defaultTenant = TenantName.defaultName(); tenants.addTenant(tenant2); tenants.createTenants(); Set<TenantName> allTenants = tenants.getAllTenantNames(); assertTrue(allTenants.contains(tenant2)); - assertEquals("default", defaultTenant.value()); - assertTrue(allTenants.contains(defaultTenant)); tenants.deleteTenant(tenant1); tenants.deleteTenant(tenant2); tenants.createTenants(); diff --git a/container-accesslogging/pom.xml b/container-accesslogging/pom.xml index 45d7b4a2ed2..b4c7094a50a 100644 --- a/container-accesslogging/pom.xml +++ b/container-accesslogging/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-accesslogging</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java b/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java index 9120c747293..24078151d64 100644 --- a/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java +++ b/container-accesslogging/src/main/java/com/yahoo/container/logging/AccessLogEntry.java @@ -4,6 +4,7 @@ package com.yahoo.container.logging; import com.yahoo.collections.ListMap; import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import javax.security.auth.x500.X500Principal; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; @@ -94,6 +95,7 @@ public class AccessLogEntry { private String scheme; private int localPort; private Principal principal; + private X500Principal sslPrincipal; private ListMap<String,String> keyValues=null; @@ -724,6 +726,19 @@ public class AccessLogEntry { } } + public Principal getSslPrincipal() { + synchronized (monitor) { + return sslPrincipal; + } + } + + public void setSslPrincipal(X500Principal sslPrincipal) { + synchronized (monitor) { + requireNull(this.sslPrincipal); + this.sslPrincipal = sslPrincipal; + } + } + @Override public String toString() { synchronized (monitor) { diff --git a/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java b/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java index cca8da2e936..85026296363 100644 --- a/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java +++ b/container-accesslogging/src/main/java/com/yahoo/container/logging/JSONFormatter.java @@ -65,10 +65,12 @@ public class JSONFormatter { Principal principal = accessLogEntry.getUserPrincipal(); if (principal != null) { - generator.writeObjectFieldStart("user-principal"); - generator.writeStringField("name", principal.getName()); - generator.writeStringField("type", principal.getClass().getName()); - generator.writeEndObject(); + generator.writeStringField("user-principal", principal.getName()); + } + + Principal sslPrincipal = accessLogEntry.getSslPrincipal(); + if (sslPrincipal != null) { + generator.writeStringField("ssl-principal", sslPrincipal.getName()); } // Only add remote address/port fields if relevant diff --git a/container-core/pom.xml b/container-core/pom.xml index c608f35f26c..1be10215419 100644 --- a/container-core/pom.xml +++ b/container-core/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-core</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java index ce9779b83d9..23c247fa438 100644 --- a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java +++ b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java @@ -190,7 +190,7 @@ public class StateHandler extends AbstractRequestHandler { private JSONObjectWithLegibleException buildJsonForConsumer(String consumer) throws JSONException { JSONObjectWithLegibleException ret = new JSONObjectWithLegibleException(); ret.put("time", timer.currentTimeMillis()); - ret.put("status", new JSONObjectWithLegibleException().put("code", "up")); + ret.put("status", new JSONObjectWithLegibleException().put("code", getStatus().name())); ret.put(METRICS_PATH, buildJsonForSnapshot(consumer, getSnapshot())); return ret; } @@ -203,6 +203,10 @@ public class StateHandler extends AbstractRequestHandler { } } + private StateMonitor.Status getStatus() { + return monitor.status(); + } + private JSONObjectWithLegibleException buildJsonForSnapshot(String consumer, MetricSnapshot metricSnapshot) throws JSONException { if (metricSnapshot == null) { return new JSONObjectWithLegibleException(); diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java index 140257cbcef..6234a96d7a0 100644 --- a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java +++ b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateMonitor.java @@ -6,6 +6,7 @@ import com.yahoo.component.AbstractComponent; import com.yahoo.container.jdisc.config.HealthMonitorConfig; import com.yahoo.jdisc.Timer; import com.yahoo.jdisc.application.MetricConsumer; +import com.yahoo.log.LogLevel; import java.util.Map; import java.util.TreeSet; @@ -14,7 +15,7 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** - * A statemonitor keeps track of the current metrics state of a container. + * A state monitor keeps track of the current health and metrics state of a container. * It is used by jDisc to hand out metric update API endpoints to workers through {@link #newMetricConsumer}, * and to inspect the current accumulated state of metrics through {@link #snapshot}. * @@ -23,12 +24,16 @@ import java.util.logging.Logger; public class StateMonitor extends AbstractComponent { private final static Logger log = Logger.getLogger(StateMonitor.class.getName()); + + public enum Status {up, down, initializing}; + private final CopyOnWriteArrayList<StateMetricConsumer> consumers = new CopyOnWriteArrayList<>(); private final Thread thread; private final Timer timer; private final long snapshotIntervalMs; private long lastSnapshotTimeMs; private volatile MetricSnapshot snapshot; + private volatile Status status; private final TreeSet<String> valueNames = new TreeSet<>(); @Inject @@ -36,13 +41,8 @@ public class StateMonitor extends AbstractComponent { this.timer = timer; this.snapshotIntervalMs = (long)(config.snapshot_interval() * TimeUnit.SECONDS.toMillis(1)); this.lastSnapshotTimeMs = timer.currentTimeMillis(); - thread = new Thread(new Runnable() { - - @Override - public void run() { - StateMonitor.this.run(); - } - }, "StateMonitor"); + this.status = Status.valueOf(config.initialStatus()); + thread = new Thread(StateMonitor.this::run, "StateMonitor"); thread.setDaemon(true); thread.start(); } @@ -54,6 +54,13 @@ public class StateMonitor extends AbstractComponent { return consumer; } + public void status(Status status) { + log.log(LogLevel.INFO, "Changing health status code from '" + this.status + "' to '" + status.name() + "'"); + this.status = status; + } + + public Status status() { return status; } + /** Returns the last snapshot taken of the metrics in this system */ public MetricSnapshot snapshot() { return snapshot; diff --git a/container-core/src/main/resources/configdefinitions/health-monitor.def b/container-core/src/main/resources/configdefinitions/health-monitor.def index dc5cdbc6ca4..5e70c72ae3f 100644 --- a/container-core/src/main/resources/configdefinitions/health-monitor.def +++ b/container-core/src/main/resources/configdefinitions/health-monitor.def @@ -4,3 +4,6 @@ namespace=container.jdisc.config # How far between snapshots. 5 minutes by default snapshot_interval double default=300 + +# Initial status used in /state/v1/health API (value for 'code' in 'status'). See StateMonitor for valid values +initialStatus string default="up" diff --git a/container-dependencies-enforcer/pom.xml b/container-dependencies-enforcer/pom.xml index 1b0a7f9e958..3384da7c1eb 100644 --- a/container-dependencies-enforcer/pom.xml +++ b/container-dependencies-enforcer/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-dependencies-enforcer</artifactId> @@ -62,8 +63,9 @@ <rules> <bannedDependencies> <excludes> - <!-- Only allow explicitly listed deps in provided scope --> + <!-- Only allow explicitly listed deps in provided and compile scope --> <exclude>*:*:*:jar:provided:*</exclude> + <exclude>*:*:*:jar:compile:*</exclude> </excludes> <includes> <include>com.yahoo.vespa</include> @@ -80,36 +82,36 @@ <include>com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:[2.5.4, ${jackson2.version}]:jar:provided</include> <include>com.fasterxml.jackson.module:jackson-module-jaxb-annotations:[2.5.4, ${jackson2.version}]:jar:provided</include> - <include>com.google.code.findbugs:annotations:[1.3.9]:jar:provided</include> - <include>com.google.code.findbugs:jsr305:[1.3.9]:jar:provided</include> - <include>com.google.guava:guava:[18.0]:jar:provided</include> - <include>com.google.inject.extensions:guice-assistedinject:[3.0]:jar:provided</include> - <include>com.google.inject.extensions:guice-multibindings:[3.0]:jar:provided</include> - <include>com.google.inject:guice:[3.0]:jar:provided:no_aop</include> + <include>com.google.code.findbugs:annotations:[${findbugs.version}]:jar:provided</include> + <include>com.google.code.findbugs:jsr305:[${findbugs.version}]:jar:provided</include> + <include>com.google.guava:guava:[${guava.version}]:jar:provided</include> + <include>com.google.inject.extensions:guice-assistedinject:[${guice.version}]:jar:provided</include> + <include>com.google.inject.extensions:guice-multibindings:[${guice.version}]:jar:provided</include> + <include>com.google.inject:guice:[${guice.version}]:jar:provided:no_aop</include> <include>commons-codec:commons-codec:[1.4]:jar:provided</include> <include>commons-daemon:commons-daemon:[1.0.3]:jar:provided</include> <include>commons-logging:commons-logging:[1.1.1]:jar:provided</include> - <include>javax.annotation:javax.annotation-api:[1.2]:jar:provided</include> + <include>javax.annotation:javax.annotation-api:[${javax.annotation-api.version}]:jar:provided</include> <include>javax.inject:javax.inject:[1]:jar:provided</include> <include>javax.servlet:javax.servlet-api:[3.1.0]:jar:provided</include> - <include>javax.validation:validation-api:[1.1.0.Final]:jar:provided</include> + <include>javax.validation:validation-api:[${javax.validation-api.version}]:jar:provided</include> <include>javax.ws.rs:javax.ws.rs-api:[${javax.ws.rs-api.version}]:jar:provided</include> <include>net.jcip:jcip-annotations:[1.0]:jar:provided</include> <include>net.jpountz.lz4:lz4:[1.3.0]:jar:provided</include> - <include>org.apache.felix:org.apache.felix.framework:[4.2.1]:jar:provided</include> + <include>org.apache.felix:org.apache.felix.framework:[${felix.version}]:jar:provided</include> <include>org.apache.felix:org.apache.felix.log:[1.0.1]:jar:provided</include> - <include>org.apache.felix:org.apache.felix.main:[4.2.1]:jar:provided</include> + <include>org.apache.felix:org.apache.felix.main:[${felix.version}]:jar:provided</include> <include>org.apache.httpcomponents:httpclient:[4.3.6]:jar:provided</include> <include>org.apache.httpcomponents:httpcore:[4.3.3]:jar:provided</include> <include>org.eclipse.jetty:jetty-http:[${jetty.version}]:jar:provided</include> <include>org.eclipse.jetty:jetty-io:[${jetty.version}]:jar:provided</include> <include>org.eclipse.jetty:jetty-util:[${jetty.version}]:jar:provided</include> - <include>org.glassfish.hk2.external:aopalliance-repackaged:[2.5.0-b05]:jar:provided</include> - <include>org.glassfish.hk2.external:javax.inject:[2.5.0-b05]:jar:provided</include> - <include>org.glassfish.hk2:hk2-api:[2.5.0-b05]:jar:provided</include> - <include>org.glassfish.hk2:hk2-locator:[2.5.0-b05]:jar:provided</include> - <include>org.glassfish.hk2:hk2-utils:[2.5.0-b05]:jar:provided</include> - <include>org.glassfish.hk2:osgi-resource-locator:[1.0.1]:jar:provided</include> + <include>org.glassfish.hk2.external:aopalliance-repackaged:[${hk2.version}]:jar:provided</include> + <include>org.glassfish.hk2.external:javax.inject:[${hk2.version}]:jar:provided</include> + <include>org.glassfish.hk2:hk2-api:[${hk2.version}]:jar:provided</include> + <include>org.glassfish.hk2:hk2-locator:[${hk2.version}]:jar:provided</include> + <include>org.glassfish.hk2:hk2-utils:[${hk2.version}]:jar:provided</include> + <include>org.glassfish.hk2:osgi-resource-locator:[${hk2.osgi-resource-locator.version}]:jar:provided</include> <include>org.glassfish.jersey.bundles.repackaged:jersey-guava:[${jersey2.version}]:jar:provided</include> <include>org.glassfish.jersey.containers:jersey-container-servlet-core:[${jersey2.version}]:jar:provided</include> <include>org.glassfish.jersey.containers:jersey-container-servlet:[${jersey2.version}]:jar:provided</include> @@ -121,13 +123,13 @@ <include>org.glassfish.jersey.media:jersey-media-jaxb:[${jersey2.version}]:jar:provided</include> <include>org.glassfish.jersey.media:jersey-media-json-jackson:[${jersey2.version}]:jar:provided</include> <include>org.glassfish.jersey.media:jersey-media-multipart:[${jersey2.version}]:jar:provided</include> - <include>org.javassist:javassist:[3.20.0-GA]:jar:provided</include> + <include>org.javassist:javassist:[${javassist.version}]:jar:provided</include> <include>org.json:json:[20090211]:jar:provided</include> - <include>org.jvnet.mimepull:mimepull:[1.9.6]:jar:provided</include> - <include>org.slf4j:jcl-over-slf4j:[1.7.5]:jar:provided</include> - <include>org.slf4j:log4j-over-slf4j:[1.7.5]:jar:provided</include> - <include>org.slf4j:slf4j-api:[1.7.5]:jar:provided</include> - <include>org.slf4j:slf4j-jdk14:[1.7.5]:jar:provided</include> + <include>org.jvnet.mimepull:mimepull:[${mimepull.version}]:jar:provided</include> + <include>org.slf4j:jcl-over-slf4j:[${slf4j.version}]:jar:provided</include> + <include>org.slf4j:log4j-over-slf4j:[${slf4j.version}]:jar:provided</include> + <include>org.slf4j:slf4j-api:[${slf4j.version}]:jar:provided</include> + <include>org.slf4j:slf4j-jdk14:[${slf4j.version}]:jar:provided</include> <include>xml-apis:xml-apis:[1.4.01]:jar:provided</include> </includes> </bannedDependencies> diff --git a/container-dependency-versions/pom.xml b/container-dependency-versions/pom.xml new file mode 100644 index 00000000000..4d639ceedab --- /dev/null +++ b/container-dependency-versions/pom.xml @@ -0,0 +1,441 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>com.yahoo.vespa</groupId> + <artifactId>container-dependency-versions</artifactId> + <packaging>pom</packaging> + <version>6-SNAPSHOT</version> + <name>container-dependency-versions</name> + <description>Versions for all 3rd party dependencies provided from the Vespa container.</description> + <url>https://github.com/vespa-engine</url> + + <licenses> + <license> + <name>The Apache License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + </license> + </licenses> + <developers> + <developer> + <name>Vespa</name> + <url>https://github.com/vespa-engine</url> + </developer> + </developers> + <distributionManagement> + <repository> + <id>bintray-vespa-repo</id> + <url>https://api.bintray.com/maven/yahoo/maven/vespa;publish=1</url> + </repository> + </distributionManagement> + <scm> + <connection>scm:git:git@github.com:vespa-engine/vespa.git</connection> + <developerConnection>scm:git:git@github.com:vespa-engine/vespa.git</developerConnection> + <url>git@github.com:vespa-engine/vespa.git</url> + </scm> + + + <!-- TODO: add pluginManagement for bundle-plugin and/or compiler-plugin? + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.6.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + <optimize>true</optimize> + </configuration> + </plugin> + <plugin> + <groupId>com.yahoo.vespa</groupId> + <artifactId>bundle-plugin</artifactId> + <version>${project.version}</version> + <configuration> + <configGenVersion>${project.version}</configGenVersion> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + --> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>aopalliance</groupId> + <artifactId>aopalliance</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jdk8</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jsr310</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.jaxrs</groupId> + <artifactId>jackson-jaxrs-base</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.jaxrs</groupId> + <artifactId>jackson-jaxrs-json-provider</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-jaxb-annotations</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>annotations</artifactId> + <version>${findbugs.version}</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>${findbugs.version}</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>${guava.version}</version> + </dependency> + <dependency> + <groupId>com.google.inject.extensions</groupId> + <artifactId>guice-assistedinject</artifactId> + <version>${guice.version}</version> + </dependency> + <dependency> + <groupId>com.google.inject.extensions</groupId> + <artifactId>guice-multibindings</artifactId> + <version>${guice.version}</version> + </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + <version>${guice.version}</version> + </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + <version>${guice.version}</version> + <classifier>no_aop</classifier> + </dependency> + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + <version>1.4</version> + </dependency> + <dependency> + <groupId>commons-daemon</groupId> + <artifactId>commons-daemon</artifactId> + <version>1.0.3</version> + </dependency> + <dependency> + <!-- This version is exported by jdisc via jcl-over-slf4j. --> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + <version>1.1.1</version> + </dependency> + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + <version>${javax.annotation-api.version}</version> + </dependency> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject</artifactId> + <version>1</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>3.1.0</version> + </dependency> + <dependency> + <groupId>javax.validation</groupId> + <artifactId>validation-api</artifactId> + <version>${javax.validation-api.version}</version> + </dependency> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>${javax.ws.rs-api.version}</version> + </dependency> + <dependency> + <groupId>net.jcip</groupId> + <artifactId>jcip-annotations</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>net.jpountz.lz4</groupId> + <artifactId>lz4</artifactId> + <version>1.3.0</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.framework</artifactId> + <version>${felix.version}</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.log</artifactId> + <version>1.0.1</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.main</artifactId> + <version>${felix.version}</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>4.3.6</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpcore</artifactId> + <version>4.3.3</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-http</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-io</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-util</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.hk2.external</groupId> + <artifactId>aopalliance-repackaged</artifactId> + <version>${hk2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.hk2.external</groupId> + <artifactId>javax.inject</artifactId> + <version>${hk2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.hk2</groupId> + <artifactId>hk2-api</artifactId> + <version>${hk2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.hk2</groupId> + <artifactId>hk2-locator</artifactId> + <version>${hk2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.hk2</groupId> + <artifactId>hk2-utils</artifactId> + <version>${hk2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.hk2</groupId> + <artifactId>osgi-resource-locator</artifactId> + <version>${hk2.osgi-resource-locator.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.bundles.repackaged</groupId> + <artifactId>jersey-guava</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.containers</groupId> + <artifactId>jersey-container-servlet-core</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.containers</groupId> + <artifactId>jersey-container-servlet</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.core</groupId> + <artifactId>jersey-client</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.core</groupId> + <artifactId>jersey-common</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.core</groupId> + <artifactId>jersey-server</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.ext</groupId> + <artifactId>jersey-entity-filtering</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.ext</groupId> + <artifactId>jersey-proxy-client</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.media</groupId> + <artifactId>jersey-media-jaxb</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.media</groupId> + <artifactId>jersey-media-json-jackson</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.glassfish.jersey.media</groupId> + <artifactId>jersey-media-multipart</artifactId> + <version>${jersey2.version}</version> + </dependency> + <dependency> + <groupId>org.javassist</groupId> + <artifactId>javassist</artifactId> + <version>${javassist.version}</version> + </dependency> + <dependency> + <groupId>org.json</groupId> + <artifactId>json</artifactId> + <version>20090211</version> + </dependency> + <dependency> + <groupId>org.jvnet.mimepull</groupId> + <artifactId>mimepull</artifactId> + <version>${mimepull.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jcl-over-slf4j</artifactId> + <version>${slf4j.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>log4j-over-slf4j</artifactId> + <version>${slf4j.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-jdk14</artifactId> + <version>${slf4j.version}</version> + </dependency> + <dependency> + <groupId>xml-apis</groupId> + <artifactId>xml-apis</artifactId> + <version>1.4.01</version> + </dependency> + + <!-- NOTE: The dependencies below are not provided from the jdisc container runtime, but had to be moved + here from 'parent' because factorylib reads the text in parent/pom.xml and this pom file to + build a pom model used to bootstrap the maven cache on factory. Hence all deps using properties + declared in this pom also have to reside in this pom. + See factorylib:com.yahoo.vespa.dependencies.pom.list.Main.--> + <!-- TODO: move these back to parent/pom.xml when the above does not hold anymore. --> + + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>com.fasterxml.jackson.jaxrs</groupId> + <artifactId>jackson-jaxrs-xml-provider</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + <version>${jackson2.version}</version> + </dependency> + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-continuation</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlets</artifactId> + <version>${jetty.version}</version> + </dependency> + <dependency> + <!-- NOT provided from jdisc runtime --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-jmx</artifactId> + <version>${jetty.version}</version> + </dependency> + + <!-- Please don't add deps here, but instead above the NOTE. --> + + </dependencies> + </dependencyManagement> + + <properties> + <felix.version>4.2.1</felix.version> + <findbugs.version>1.3.9</findbugs.version> + <guava.version>18.0</guava.version> + <guice.version>3.0</guice.version> + <jetty.version>9.4.8.v20171121</jetty.version> + <slf4j.version>1.7.5</slf4j.version> + + <!-- These must be kept in sync with version used by current jersey2.version. --> + <!-- MUST be updated each time jersey2 is upgraded! --> + <!-- Check versions by doing: ' ls -l vespa/vespa_jersey2/target/dependency' --> + <hk2.version>2.5.0-b05</hk2.version> + <hk2.osgi-resource-locator.version>1.0.1</hk2.osgi-resource-locator.version> + <jackson2.version>2.8.3</jackson2.version> + <javassist.version>3.20.0-GA</javassist.version> + <javax.annotation-api.version>1.2</javax.annotation-api.version> + <javax.validation-api.version>1.1.0.Final</javax.validation-api.version> + <javax.ws.rs-api.version>2.0.1</javax.ws.rs-api.version> + <jersey2.version>2.23.2</jersey2.version> + <mimepull.version>1.9.6</mimepull.version> + </properties> + +</project> diff --git a/container-dev-builder/OWNERS b/container-dev-builder/OWNERS deleted file mode 100644 index 3b2ba1ede81..00000000000 --- a/container-dev-builder/OWNERS +++ /dev/null @@ -1 +0,0 @@ -gjoranv diff --git a/container-dev-builder/README.md b/container-dev-builder/README.md deleted file mode 100644 index 3218833930a..00000000000 --- a/container-dev-builder/README.md +++ /dev/null @@ -1,4 +0,0 @@ -<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> -# container-dev-builder - -TODO diff --git a/container-dev-builder/dependency_blacklist b/container-dev-builder/dependency_blacklist deleted file mode 100644 index 7b0d647464d..00000000000 --- a/container-dev-builder/dependency_blacklist +++ /dev/null @@ -1,2 +0,0 @@ -antlr:antlr -junit:junit diff --git a/container-dev-builder/make.sh b/container-dev-builder/make.sh deleted file mode 100755 index c434b13ee7b..00000000000 --- a/container-dev-builder/make.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -export VERSION=$1 -if [ -z "$VERSION" ] -then - echo "usage: $(basename $0) <version> [<builddir>]" - exit 1 -fi -echo VERSION=$VERSION - -export BASEDIR=$2 -if [ "$BASEDIR" ] -then - export POM_FORMAT="$BASEDIR/%s-HEAD/%s/pom.xml" -else - export POM_FORMAT="$(pwd)/../%s/pom.xml" -fi -echo POM_FORMAT=$POM_FORMAT -echo - -echo "-------------------------------------------------------------------------------" -echo "Compiling toolkit." -echo "-------------------------------------------------------------------------------" -cd tools -mvn -q clean install -DskipTests -JAVA_TOOLS="java -cp $(pwd)/target/tools-jar-with-dependencies.jar" -echo -cd .. - -DEPS_NEXT="com.yahoo.vespa:container-disc:jar:$VERSION:compile" -DEPS_PREV="" -BLACKLIST=$(cat dependency_blacklist) - -PASS=0 -rm -rf target -mkdir target -cd target -while [ "$DEPS_NEXT" ] -do - PASS=$((PASS+1)) - mkdir -p "pass$PASS" - cd "pass$PASS" - - DEPS_PREV=$DEPS_NEXT - DEPS_NEXT="" - - echo "-------------------------------------------------------------------------------" - echo "Deriving dependencies, pass $PASS." - echo "-------------------------------------------------------------------------------" - echo DEPENDENCIES=$DEPS_PREV - echo "Building dependency tree.." - $JAVA_TOOLS com.yahoo.container.dev.builder.PomFileGenerator $VERSION $DEPS_PREV > pom.xml - mvn -q dependency:tree -DoutputFile=dependencies - DEPS_NEXT="$($JAVA_TOOLS com.yahoo.container.dev.builder.DependencyResolver . $BLACKLIST)" - - echo "Resolving X-JDisc-Preinstall-Bundle instructions.." - mvn -q dependency:unpack-dependencies \ - -DexcludeTransitive=true \ - -Dmdep.unpack.includes="META-INF/MANIFEST.MF" \ - -Dmdep.useSubDirectoryPerArtifact=true \ - -DoutputDirectory=. - DEPS_NEXT="$DEPS_NEXT $($JAVA_TOOLS com.yahoo.container.dev.builder.PreinstalledBundleResolver . "$POM_FORMAT")" - - DEPS_NEXT=$(echo $DEPS_NEXT | sort | uniq) - [ "$DEPS_NEXT" == "$DEPS_PREV" ] && DEPS_NEXT="" - echo - cd .. -done - -echo "-------------------------------------------------------------------------------" -echo "Testing final pom.xml" -echo "-------------------------------------------------------------------------------" -cp pass$PASS/pom.xml . -mvn clean install -DskipTests || exit 1 -cd .. diff --git a/container-dev-builder/tools/pom.xml b/container-dev-builder/tools/pom.xml deleted file mode 100644 index 3c1132e439d..00000000000 --- a/container-dev-builder/tools/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0"?> -<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 - http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>tools</groupId> - <artifactId>tools</artifactId> - <version>1.0</version> - <dependencies> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-core</artifactId> - <version>3.1.1</version> - </dependency> - </dependencies> - <build> - <finalName>${project.artifactId}</finalName> - <plugins> - <plugin> - <artifactId>maven-compiler-plugin</artifactId> - <version>3.6.1</version> - <configuration> - <source>1.8</source> - <target>1.8</target> - </configuration> - </plugin> - <plugin> - <artifactId>maven-assembly-plugin</artifactId> - <version>2.4</version> - <configuration> - <descriptorRefs> - <descriptorRef>jar-with-dependencies</descriptorRef> - </descriptorRefs> - </configuration> - <executions> - <execution> - <id>make-assembly</id> - <phase>package</phase> - <goals> - <goal>single</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - </properties> -</project> diff --git a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java b/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java deleted file mode 100644 index 5ebf5488b02..00000000000 --- a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/DependencyResolver.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.dev.builder; - -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.TreeSet; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> - */ -public class DependencyResolver { - - private static final Path DEPENDENCIES = Paths.get("dependencies"); - - public static void main(String[] args) throws IOException, XmlPullParserException { - final Set<String> blacklist = new HashSet<>(Arrays.asList(args).subList(1, args.length)); - final Set<String> dependencies = new TreeSet<>(); - Files.walkFileTree(Paths.get(args[0]), new SimpleFileVisitor<Path>() { - - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - if (!attrs.isRegularFile()) { - return FileVisitResult.CONTINUE; - } - if (!file.getFileName().equals(DEPENDENCIES)) { - return FileVisitResult.CONTINUE; - } - for (final String line : Files.readAllLines(file, StandardCharsets.UTF_8)) { - for (final String dependency : line.split(" ")) { - if (dependency == null || dependency.isEmpty()) { - continue; - } - final String[] arr = dependency.split(":"); - if (arr.length != 5 || blacklist.contains(arr[0] + ":" + arr[1])) { - continue; - } - dependencies.add(dependency); - } - } - return FileVisitResult.CONTINUE; - } - }); - for (final String dependency : dependencies) { - System.out.println(dependency); - } - } -} diff --git a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java b/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java deleted file mode 100644 index 7eb884034d6..00000000000 --- a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PomFileGenerator.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.dev.builder; - -import org.apache.maven.model.Dependency; -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Writer; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.TreeSet; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> - */ -public class PomFileGenerator { - - public static void main(String[] args) throws IOException { - Model model = new Model(); - model.setModelVersion("4.0.0"); - model.setGroupId("com.yahoo.vespa"); - model.setArtifactId("container-dev"); - model.setVersion(args[0]); - model.getProperties().setProperty("project.build.sourceEncoding", StandardCharsets.UTF_8.name()); - for (String str : new TreeSet<>(Arrays.asList(args).subList(1, args.length))) { - Dependency dependency = newDependency(str); - if (dependency == null) { - continue; - } - if (dependency.getGroupId().equals(model.getGroupId()) && - dependency.getArtifactId().equals(model.getArtifactId())) { - continue; - } - model.addDependency(dependency); - } - new MavenXpp3Writer().write(System.out, model); - } - - private static Dependency newDependency(String str) { - String[] arr = str.split(":"); - if (arr.length != 5) { - return null; - } - Dependency out = new Dependency(); - out.setGroupId(arr[0]); - out.setArtifactId(arr[1]); - out.setVersion(arr[3]); - return out; - } -} diff --git a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java b/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java deleted file mode 100644 index b9187f5a026..00000000000 --- a/container-dev-builder/tools/src/main/java/com/yahoo/container/dev/builder/PreinstalledBundleResolver.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.dev.builder; - -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.jar.Manifest; - -public class PreinstalledBundleResolver { - - private static final Path MANIFEST_MF = Paths.get("MANIFEST.MF"); - private static final String X_JDISC_PREINSTALL_BUNDLE = "X-JDisc-Preinstall-Bundle"; - private static final String REMOVABLE_SUFFIX = ".jar"; - private static final String REMOVABLE_ASSEMBLY_ID = "-jar-with-dependencies"; - - public static void main(final String[] args) throws Throwable { - Files.walkFileTree(Paths.get(args[0]), new SimpleFileVisitor<Path>() { - - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - if (!attrs.isRegularFile()) { - return FileVisitResult.CONTINUE; - } - if (!file.getFileName().equals(MANIFEST_MF)) { - return FileVisitResult.CONTINUE; - } - final String preinstall = new Manifest(Files.newInputStream(file)) - .getMainAttributes() - .getValue(X_JDISC_PREINSTALL_BUNDLE); - if (preinstall == null) { - return FileVisitResult.CONTINUE; - } - for (String bundle : preinstall.split(",")) { - printDependency(args[1], bundle); - } - return super.visitFile(file, attrs); - } - }); - } - - private static void printDependency(String pomFormat, String bundle) throws IOException { - bundle = bundle.trim(); - if (bundle.isEmpty()) { - return; - } - if (bundle.endsWith(REMOVABLE_SUFFIX)) { - bundle = bundle.substring(0, bundle.length() - REMOVABLE_SUFFIX.length()); - } - if (bundle.endsWith(REMOVABLE_ASSEMBLY_ID)) { - bundle = bundle.substring(0, bundle.length() - REMOVABLE_ASSEMBLY_ID.length()); - } - Path pom = Paths.get(String.format(pomFormat, bundle)); - if (!Files.exists(pom)) { - return; - } - Model model; - try { - model = new MavenXpp3Reader().read(Files.newBufferedReader(pom, StandardCharsets.UTF_8)); - } catch (XmlPullParserException e) { - e.printStackTrace(); - return; - } - model.setPomFile(pom.toFile()); - final MavenProject project = new MavenProject(model); - System.out.println(project.getGroupId() + ":" + - project.getArtifactId() + ":jar:" + - project.getVersion() + ":compile"); - } -} diff --git a/container-dev/pom.xml b/container-dev/pom.xml index d02ec233d96..f62bbd22690 100644 --- a/container-dev/pom.xml +++ b/container-dev/pom.xml @@ -10,6 +10,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-dev</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-di/pom.xml b/container-di/pom.xml index cf6a9e54daa..abc652d6e6e 100644 --- a/container-di/pom.xml +++ b/container-di/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-di</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-disc/pom.xml b/container-disc/pom.xml index ad86b3ffcff..38b4bfc2ff5 100644 --- a/container-disc/pom.xml +++ b/container-disc/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-disc</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-jersey2/pom.xml b/container-jersey2/pom.xml index 8f579e49876..26dfa762032 100644 --- a/container-jersey2/pom.xml +++ b/container-jersey2/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-jersey2</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-messagebus/pom.xml b/container-messagebus/pom.xml index 6058063daf3..1daf1df6fcb 100644 --- a/container-messagebus/pom.xml +++ b/container-messagebus/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-messagebus</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-search-and-docproc/pom.xml b/container-search-and-docproc/pom.xml index fb2794670a2..9b4419ae2b6 100644 --- a/container-search-and-docproc/pom.xml +++ b/container-search-and-docproc/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-search-and-docproc</artifactId> <version>6-SNAPSHOT</version> diff --git a/container-search/pom.xml b/container-search/pom.xml index f622567acde..0df502cf21a 100644 --- a/container-search/pom.xml +++ b/container-search/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-search</artifactId> <packaging>jar</packaging> diff --git a/container-test-jars/pom.xml b/container-test-jars/pom.xml index dcde18fc8cf..804ca5eb879 100644 --- a/container-test-jars/pom.xml +++ b/container-test-jars/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa.container-test-jars</groupId> <artifactId>container-test-jars</artifactId> diff --git a/container-test/pom.xml b/container-test/pom.xml index 7aaffed652e..26f02767062 100644 --- a/container-test/pom.xml +++ b/container-test/pom.xml @@ -11,6 +11,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container-test</artifactId> <version>6-SNAPSHOT</version> diff --git a/container/pom.xml b/container/pom.xml index 4726071db55..3793a3508a4 100644 --- a/container/pom.xml +++ b/container/pom.xml @@ -11,6 +11,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>container</artifactId> <version>6-SNAPSHOT</version> diff --git a/controller-api/pom.xml b/controller-api/pom.xml index 51666da0c03..5ef130a22ba 100644 --- a/controller-api/pom.xml +++ b/controller-api/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>controller-api</artifactId> <packaging>container-plugin</packaging> diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java index 80fe98a4489..f1c584c7a3c 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/DeploymentId.java @@ -1,57 +1,54 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.identifiers; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; + +import java.util.Objects; /** - * Application + zone. + * ApplicationId x ZoneId. * * @author smorgrav * @author bratseth */ public class DeploymentId { - private final com.yahoo.config.provision.ApplicationId application; - private final Zone zone; + private final com.yahoo.config.provision.ApplicationId applicationId; + private final ZoneId zoneId; - public DeploymentId(com.yahoo.config.provision.ApplicationId application, Zone zone) { - this.application = application; - this.zone = zone; + public DeploymentId(com.yahoo.config.provision.ApplicationId applicationId, ZoneId zoneId) { + this.applicationId = applicationId; + this.zoneId = zoneId; } public com.yahoo.config.provision.ApplicationId applicationId() { - return application; + return applicationId; } - public Zone zone() { return zone; } + public ZoneId zoneId() { + return zoneId; + } public String dottedString() { return unCapitalize(applicationId().tenant().value()) + "." - + unCapitalize(applicationId().application().value()) + "." - + unCapitalize(zone.environment().value()) + "." - + unCapitalize(zone.region().value()) + "." - + unCapitalize(application.instance().value()); + + unCapitalize(applicationId().application().value()) + "." + + unCapitalize(zoneId.environment().value()) + "." + + unCapitalize(zoneId.region().value()) + "." + + unCapitalize(applicationId.instance().value()); } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - DeploymentId other = (DeploymentId) o; - if ( ! this.application.equals(other.application)) return false; - // TODO: Simplify when Zone implements equals - if ( ! this.zone.environment().equals(other.zone.environment())) return false; - if ( ! this.zone.region().equals(other.zone.region())) return false; - return true; + if ( ! (o instanceof DeploymentId)) return false; + DeploymentId id = (DeploymentId) o; + return Objects.equals(applicationId, id.applicationId) && + Objects.equals(zoneId, id.zoneId); } @Override public int hashCode() { - // TODO: Simplify when Zone implements hashCode - return application.hashCode() + - 7 * zone.environment().hashCode() + - 31 * zone.region().hashCode(); + return Objects.hash(applicationId, zoneId); } @Override @@ -60,10 +57,11 @@ public class DeploymentId { } public String toUserFriendlyString() { - return application + " in " + zone; + return applicationId + " in " + zoneId; } private static String unCapitalize(String str) { return str.toLowerCase().substring(0,1) + str.substring(1); } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java index aab18595d20..2eeb0f60748 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/RotationId.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.api.identifiers; /** * @author smorgrav */ +// TODO: Used in serialization (ConfigServerClient). Remove when no longer used by ControllerDb and ConfigServerClient public class RotationId extends Identifier { public RotationId(String id) { diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java deleted file mode 100644 index 79210143d19..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/ZoneId.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.identifiers; - -/** - * @author smorgrav - */ -public class ZoneId extends Identifier { - - public ZoneId(EnvironmentId envId, RegionId regionId) { - super(envId.id() + ":" + regionId.id()); - } - -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java index 1ceb064ad44..d3e1b881bbd 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import java.util.Map; @@ -15,9 +15,9 @@ public interface MetricsService { ApplicationMetrics getApplicationMetrics(ApplicationId application); - DeploymentMetrics getDeploymentMetrics(ApplicationId application, Zone zone); + DeploymentMetrics getDeploymentMetrics(ApplicationId application, ZoneId zone); - Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, Zone zone); + Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, ZoneId zone); class DeploymentMetrics { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java index 8614414dc95..3323cda89b3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; /** * @author bjorncs diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java index b6a21f94f74..a2a16d10cdb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzClientFactory.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; /** * @author bjorncs diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java new file mode 100644 index 00000000000..ef63ef2581f --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentity.java @@ -0,0 +1,16 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; + +/** + * @author bjorncs + */ +public interface AthenzIdentity { + AthenzDomain getDomain(); + String getName(); + default String getFullName() { + return getDomain().id() + "." + getName(); + } +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java new file mode 100644 index 00000000000..d53817c09e4 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityCertificate.java @@ -0,0 +1,27 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * @author bjorncs + */ +public class AthenzIdentityCertificate { + + private final X509Certificate certificate; + private final PrivateKey privateKey; + + public AthenzIdentityCertificate(X509Certificate certificate, PrivateKey privateKey) { + this.certificate = certificate; + this.privateKey = privateKey; + } + + public X509Certificate getCertificate() { + return certificate; + } + + public PrivateKey getPrivateKey() { + return privateKey; + } +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java new file mode 100644 index 00000000000..8279edcd8e6 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPrincipal.java @@ -0,0 +1,59 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; + +import java.security.Principal; +import java.util.Objects; + +/** + * @author bjorncs + */ +public class AthenzPrincipal implements Principal { + + private final AthenzIdentity athenzIdentity; + private final NToken nToken; + + public AthenzPrincipal(AthenzIdentity athenzIdentity, + NToken nToken) { + this.athenzIdentity = athenzIdentity; + this.nToken = nToken; + } + + public AthenzIdentity getIdentity() { + return athenzIdentity; + } + + @Override + public String getName() { + return athenzIdentity.getFullName(); + } + + public AthenzDomain getDomain() { + return athenzIdentity.getDomain(); + } + + public NToken getNToken() { + return nToken; + } + + @Override + public String toString() { + return "AthenzPrincipal{" + + "athenzIdentity=" + athenzIdentity + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AthenzPrincipal principal = (AthenzPrincipal) o; + return Objects.equals(athenzIdentity, principal.athenzIdentity); + } + + @Override + public int hashCode() { + return Objects.hash(athenzIdentity); + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPublicKey.java index 01596ead0f4..c7f370dd4e3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPublicKey.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzPublicKey.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; import java.security.PublicKey; import java.util.Objects; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java new file mode 100644 index 00000000000..80548cccd89 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzRoleCertificate.java @@ -0,0 +1,27 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * @author bjorncs + */ +public class AthenzRoleCertificate { + + private final X509Certificate certificate; + private final PrivateKey privateKey; + + public AthenzRoleCertificate(X509Certificate certificate, PrivateKey privateKey) { + this.certificate = certificate; + this.privateKey = privateKey; + } + + public X509Certificate getCertificate() { + return certificate; + } + + public PrivateKey getPrivateKey() { + return privateKey; + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzService.java index 37c6459b687..24cd7671d96 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzService.java @@ -1,14 +1,15 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import java.util.Objects; /** * @author bjorncs */ -public class AthenzService { +public class AthenzService implements AthenzIdentity { private final AthenzDomain domain; private final String serviceName; @@ -22,15 +23,17 @@ public class AthenzService { this(new AthenzDomain(domain), serviceName); } - public String toFullServiceName() { - return domain.id() + "." + serviceName; + public static AthenzService fromScrewdriverId(ScrewdriverId screwdriverId) { + return new AthenzService(AthenzUtils.SCREWDRIVER_DOMAIN, "sd" + screwdriverId.id()); } + @Override public AthenzDomain getDomain() { return domain; } - public String getServiceName() { + @Override + public String getName() { return serviceName; } @@ -50,6 +53,6 @@ public class AthenzService { @Override public String toString() { - return String.format("AthenzService(%s)", toFullServiceName()); + return String.format("AthenzService(%s)", getFullName()); } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java new file mode 100644 index 00000000000..480105a2d86 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzSslContextProvider.java @@ -0,0 +1,14 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import com.google.inject.Provider; + +import javax.net.ssl.SSLContext; + +/** + * Provides a {@link SSLContext} for use in controller clients communicating with Athenz TLS secured services. + * It is configured with a keystore containing the Athenz service certificate and a trust store with the Athenz CA certificates. + * + * @author bjorncs + */ +public interface AthenzSslContextProvider extends Provider<SSLContext> {} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUser.java index 1e4952a39c5..782876f21f1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUser.java @@ -1,35 +1,28 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import java.security.Principal; import java.util.Objects; /** * @author bjorncs */ -public class AthenzPrincipal implements Principal { - - private final AthenzDomain domain; +public class AthenzUser implements AthenzIdentity { private final UserId userId; - public AthenzPrincipal(AthenzDomain domain, UserId userId) { - this.domain = domain; + public AthenzUser(UserId userId) { this.userId = userId; } - public UserId getUserId() { - return userId; + public static AthenzUser fromUserId(UserId userId) { + return new AthenzUser(userId); } + @Override public AthenzDomain getDomain() { - return domain; - } - - public String toYRN() { - return domain.id() + "." + userId.id(); + return AthenzUtils.USER_PRINCIPAL_DOMAIN; } @Override @@ -37,11 +30,14 @@ public class AthenzPrincipal implements Principal { return userId.id(); } + public UserId getUserId() { + return userId; + } + @Override public String toString() { - return "AthenzPrincipal{" + - "domain=" + domain + - ", userId=" + userId + + return "AthenzUser{" + + "userId=" + userId + '}'; } @@ -49,14 +45,12 @@ public class AthenzPrincipal implements Principal { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AthenzPrincipal that = (AthenzPrincipal) o; - return Objects.equals(domain, that.domain) && - Objects.equals(userId, that.userId); + AthenzUser that = (AthenzUser) o; + return Objects.equals(userId, that.userId); } @Override public int hashCode() { - return Objects.hash(domain, userId); + return Objects.hash(userId); } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUtils.java index 18bd626369d..0ed5d86dd7e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzUtils.java @@ -1,8 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; /** @@ -16,13 +15,12 @@ public class AthenzUtils { public static final AthenzDomain SCREWDRIVER_DOMAIN = new AthenzDomain("cd.screwdriver.project"); public static final AthenzService ZMS_ATHENZ_SERVICE = new AthenzService("sys.auth", "zms"); - public static AthenzPrincipal createPrincipal(UserId userId) { - return new AthenzPrincipal(USER_PRINCIPAL_DOMAIN, userId); + public static AthenzIdentity createAthenzIdentity(AthenzDomain domain, String identityName) { + if (domain.equals(USER_PRINCIPAL_DOMAIN)) { + return AthenzUser.fromUserId(new UserId(identityName)); + } else { + return new AthenzService(domain, identityName); + } } - public static AthenzPrincipal createPrincipal(ScrewdriverId screwdriverId) { - return new AthenzPrincipal(SCREWDRIVER_DOMAIN, new UserId("sd" + screwdriverId.id())); - } - - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/InvalidTokenException.java index e41bd8d4283..1df1746b02e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/InvalidTokenException.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/InvalidTokenException.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; /** * @author bjorncs diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java new file mode 100644 index 00000000000..c2796befdc8 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/NToken.java @@ -0,0 +1,36 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import java.util.Objects; + +/** + * Represents an Athenz NToken (principal token) + * + * @author bjorncs + */ +public class NToken { + + private final String rawToken; + + public NToken(String rawToken) { + this.rawToken = rawToken; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NToken nToken = (NToken) o; + return Objects.equals(rawToken, nToken.rawToken); + } + + public String getRawToken() { + return rawToken; + } + + @Override + public int hashCode() { + return Objects.hash(rawToken); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java new file mode 100644 index 00000000000..cfa63b04197 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZToken.java @@ -0,0 +1,36 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import java.util.Objects; + +/** + * Represents an Athenz ZToken (role token) + * + * @author bjorncs + */ +public class ZToken { + + private final String rawToken; + + public ZToken(String rawToken) { + this.rawToken = rawToken; + } + + public String getRawToken() { + return rawToken; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ZToken zToken = (ZToken) o; + return Objects.equals(rawToken, zToken.rawToken); + } + + @Override + public int hashCode() { + return Objects.hash(rawToken); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java index 407bce05c6e..d72b8960427 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; @@ -19,12 +19,12 @@ public interface ZmsClient { void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName); - boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName); + boolean hasApplicationAccess(AthenzIdentity athenzIdentity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName); - boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain); + boolean hasTenantAdminAccess(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain); // Used before vespa tenancy is established for the domain. - boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain); + boolean isDomainAdmin(AthenzIdentity athenzIdentity, AthenzDomain domain); List<AthenzDomain> getDomainList(String prefix); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java new file mode 100644 index 00000000000..31e9e549c08 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java @@ -0,0 +1,24 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +/** + * @author bjorncs + */ +public class ZmsException extends RuntimeException { + + private final int code; + + public ZmsException(int code, Throwable cause) { + super(cause.getMessage(), cause); + this.code = code; + } + + public ZmsException(int code, String message) { + super(message); + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsKeystore.java index 93fed95c768..e2cb38a8466 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsKeystore.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsKeystore.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; import java.security.PublicKey; import java.util.Optional; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java new file mode 100644 index 00000000000..f37c1679d1e --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClient.java @@ -0,0 +1,19 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; + +import java.util.List; + +/** + * @author bjorncs + */ +public interface ZtsClient { + + List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity principal); + + AthenzIdentityCertificate getIdentityCertificate(); + + AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName); + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsException.java index cb0b21ba459..2be998e1544 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsException.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsException.java @@ -1,7 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.athenz.zts.ZTSClientException; +package com.yahoo.vespa.hosted.controller.api.integration.athenz; /** * @author bjorncs @@ -10,12 +8,11 @@ public class ZtsException extends RuntimeException { private final int code; - public ZtsException(ZTSClientException e) { - super(e.getMessage(), e); - this.code = e.getCode(); + public ZtsException(int code, Throwable cause) { + super(cause.getMessage(), cause); + this.code = code; } - public int getCode() { return code; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java new file mode 100644 index 00000000000..d66525275bc --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.vespa.hosted.controller.api.integration.athenz; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java index 10f9e18fa41..ec9cf0b3436 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java @@ -2,9 +2,9 @@ package com.yahoo.vespa.hosted.controller.api.integration.dns; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -15,22 +15,41 @@ import java.util.UUID; */ public class MemoryNameService implements NameService { - private final List<Record> records = new ArrayList<>(); + private final Map<RecordId, Record> records = new HashMap<>(); - public List<Record> records() { - return Collections.unmodifiableList(records); + public Map<RecordId, Record> records() { + return Collections.unmodifiableMap(records); } @Override - public RecordId createCname(String alias, String canonicalName) { - records.add(new Record(Record.Type.CNAME.name(), alias, canonicalName)); - return new RecordId(UUID.randomUUID().toString()); + public RecordId createCname(RecordName alias, RecordData canonicalName) { + RecordId id = new RecordId(UUID.randomUUID().toString()); + records.put(id, new Record(id, Record.Type.CNAME, alias, canonicalName)); + return id; } @Override - public Optional<Record> findRecord(Record.Type type, String name) { - return records.stream() + public Optional<Record> findRecord(Record.Type type, RecordName name) { + return records.values().stream() .filter(record -> record.type() == type && record.name().equals(name)) .findFirst(); } + + @Override + public Optional<Record> findRecord(Record.Type type, RecordData data) { + return records.values() + .stream() + .filter(record -> record.type() == type && record.data().equals(data)) + .findFirst(); + } + + @Override + public void updateRecord(RecordId id, RecordData newData) { + records.computeIfPresent(id, (k, record) -> new Record(id, record.type(), record.name(), newData)); + } + + @Override + public void removeRecord(RecordId id) { + records.remove(id); + } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java index 2ccce23b60c..078a7e7cefb 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java @@ -14,11 +14,20 @@ public interface NameService { * Create a new CNAME record * * @param alias The alias to create - * @param canonicalName The canonical name which the alias should point to. This must be a domain. + * @param canonicalName The canonical name which the alias should point to. This must be a FQDN. */ - RecordId createCname(String alias, String canonicalName); + RecordId createCname(RecordName alias, RecordData canonicalName); /** Find record by type and name */ - Optional<Record> findRecord(Record.Type type, String name); + Optional<Record> findRecord(Record.Type type, RecordName name); + + /** Find record by type and data */ + Optional<Record> findRecord(Record.Type type, RecordData data); + + /** Update existing record */ + void updateRecord(RecordId id, RecordData newData); + + /** Remove record by ID */ + void removeRecord(RecordId id); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java index 0782a82da79..b51202e8261 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java @@ -4,35 +4,41 @@ package com.yahoo.vespa.hosted.controller.api.integration.dns; import java.util.Objects; /** - * A basic representation of a DNS resource record, containing only the record type, name and value. + * A basic representation of a DNS resource record, containing the record id, type, name and value. * * @author mpolden */ public class Record { + private final RecordId id; private final Type type; - private final String name; - private final String value; + private final RecordName name; + private final RecordData data; - public Record(Type type, String name, String value) { - this.type = type; - this.name = name; - this.value = value; + public Record(RecordId id, Type type, RecordName name, RecordData data) { + this.id = Objects.requireNonNull(id, "id cannot be null"); + this.type = Objects.requireNonNull(type, "type cannot be null"); + this.name = Objects.requireNonNull(name, "name cannot be null"); + this.data = Objects.requireNonNull(data, "data cannot be null"); } - public Record(String type, String name, String value) { - this(Type.valueOf(type), name, value); + /** Unique identifier for this */ + public RecordId id() { + return id; } + /** DNS type of this */ public Type type() { return type; } - public String value() { - return value; + /** Data in this, e.g. IP address for "A" record */ + public RecordData data() { + return data; } - public String name() { + /** Name of this, e.g. a FQDN for "A" record */ + public RecordName name() { return name; } @@ -51,24 +57,26 @@ public class Record { @Override public String toString() { return "Record{" + - "type=" + type + - ", name='" + name + '\'' + - ", value='" + value + '\'' + - '}'; + "id=" + id + + ", type=" + type + + ", name='" + name + '\'' + + ", data='" + data + '\'' + + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof Record)) return false; + if (o == null || getClass() != o.getClass()) return false; Record record = (Record) o; - return type == record.type && - Objects.equals(name, record.name); + return Objects.equals(id, record.id) && + type == record.type && + Objects.equals(name, record.name) && + Objects.equals(data, record.data); } @Override public int hashCode() { - return Objects.hash(type, name); + return Objects.hash(id, type, name, data); } - } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java new file mode 100644 index 00000000000..e0d19e0fff9 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordData.java @@ -0,0 +1,55 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.dns; + +import java.util.Objects; + +/** + * Represents the data field of a DNS record (RDATA). + * + * E.g. this may be an IP address for A records, or a FQDN for CNAME records. + * + * @author mpolden + */ +public class RecordData { + + private final String data; + + private RecordData(String data) { + this.data = Objects.requireNonNull(data, "data cannot be null"); + } + + public String asString() { + return data; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RecordData that = (RecordData) o; + return Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public String toString() { + return "RecordValue{" + + "value='" + data + '\'' + + '}'; + } + + /** Create a new record containing the given data */ + public static RecordData from(String data) { + return new RecordData(data); + } + + /** Create a new record and append a trailing dot to given data, if missing */ + public static RecordData fqdn(String data) { + return from(data.endsWith(".") ? data : data + "."); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java index 9c47be12855..da42c38252a 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordId.java @@ -14,7 +14,7 @@ public class RecordId { this.id = id; } - public String id() { + public String asString() { return id; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java new file mode 100644 index 00000000000..aa239ece588 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/RecordName.java @@ -0,0 +1,47 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.dns; + +import java.util.Objects; + +/** + * Represents the name field of a DNS record (NAME). This is typically a FQDN. + * + * @author mpolden + */ +public class RecordName { + + private final String name; + + private RecordName(String name) { + this.name = Objects.requireNonNull(name, "name cannot be null"); + } + + public String asString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RecordName that = (RecordName) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "RecordName{" + + "name='" + name + '\'' + + '}'; + } + + public static RecordName from(String name) { + return new RecordName(name); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java index df182b56fd8..37356f2c2a6 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java @@ -19,8 +19,9 @@ public class Issue { private final String label; private final User assignee; private final PropertyId propertyId; + private final Type type; - private Issue(String summary, String description, String label, User assignee, PropertyId propertyId) { + private Issue(String summary, String description, String label, User assignee, PropertyId propertyId, Type type) { if (summary.isEmpty()) throw new IllegalArgumentException("Issue summary can not be empty!"); if (description.isEmpty()) throw new IllegalArgumentException("Issue description can not be empty!"); Objects.requireNonNull(propertyId, "An issue must belong to a property!"); @@ -30,26 +31,31 @@ public class Issue { this.label = label; this.assignee = assignee; this.propertyId = propertyId; + this.type = type; } public Issue(String summary, String description, PropertyId propertyId) { - this(summary, description, null, null, propertyId); + this(summary, description, null, null, propertyId, Type.defect); } public Issue append(String appendage) { - return new Issue(summary, description + appendage, label, assignee, propertyId); + return new Issue(summary, description + appendage, label, assignee, propertyId, type); } - public Issue withLabel(String label) { - return new Issue(summary, description, label, assignee, propertyId); + public Issue with(String label) { + return new Issue(summary, description, label, assignee, propertyId, type); } - public Issue withAssignee(User assignee) { - return new Issue(summary, description, label, assignee, propertyId); + public Issue with(User assignee) { + return new Issue(summary, description, label, assignee, propertyId, type); } - public Issue withPropertyId(PropertyId propertyId) { - return new Issue(summary, description, label, assignee, propertyId); + public Issue with(PropertyId propertyId) { + return new Issue(summary, description, label, assignee, propertyId, type); + } + + public Issue with(Type type) { + return new Issue(summary, description, label, assignee, propertyId, type); } public String summary() { @@ -72,4 +78,16 @@ public class Issue { return propertyId; } + public Type type() { + return type; + } + + + public enum Type { + + defect, // A defect which needs fixing. + task // A task the humans must perform. + + } + } 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 df6f023f6af..af7c464b8d7 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 @@ -5,7 +5,8 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import java.net.URI; import java.time.Duration; @@ -19,14 +20,38 @@ import java.util.Optional; */ public interface ZoneRegistry { - SystemName system(); - List<Zone> zones(); - Optional<Zone> getZone(Environment environment, RegionName region); - List<URI> getConfigServerUris(Environment environment, RegionName region); - Optional<URI> getLogServerUri(Environment environment, RegionName region); - Optional<Duration> getDeploymentTimeToLive(Environment environment, RegionName region); + /** Returns whether the system of this registry contains the given zone. */ + boolean hasZone(ZoneId zoneId); + + /** Returns a list containing the id of all zones in this registry. */ + List<ZoneId> zones(); + + /** Returns the default region for the given environment, if one is configured. */ Optional<RegionName> getDefaultRegion(Environment environment); - URI getMonitoringSystemUri(Environment environment, RegionName name, ApplicationId application); + + /** Returns a list with all known config servers in the given zone. + * + * @deprecated Use {@link #getConfigServerSecureUris(ZoneId)} instead (requires that client trusts Athenz CA) + */ + @Deprecated + List<URI> getConfigServerUris(ZoneId zoneId); + + /** Returns a list with all known config servers in the given zone, with a secure connection URL. */ + List<URI> getConfigServerSecureUris(ZoneId zoneId); + + /** Returns a URL with the logs for the given deployment, if loggin is configured for its zone. */ + Optional<URI> getLogServerUri(DeploymentId deploymentId); + + /** Returns the time to live for deployments in the given zone, or empty if this is infinite. */ + Optional<Duration> getDeploymentTimeToLive(ZoneId zoneId); + + /** Returns a URL pointing at monitoring resources for the given deployment. */ + URI getMonitoringSystemUri(DeploymentId deploymentId); + + /** Returns the URL of the dashboard for the system of this registry. */ URI getDashboardUri(); + /** Returns the system of this registry. */ + SystemName system(); + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java index ed3e69bcac7..a1f78302e4b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/rotation/Rotation.java @@ -10,6 +10,8 @@ import java.util.Objects; * * @author Oyvind Gronnesby */ +// TODO: Used in serialization (ConfigServerClient). This should be removed and config server client should use a +// Set<String> instead, like it does for CNAMEs. public class Rotation { /** The ID of the allocated rotation. This value is generated by global routing system. */ diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java index 97d99e262b5..1f1350ca001 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/zone/v2/ZoneApiV2.java @@ -8,7 +8,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId; import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -18,7 +17,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -44,32 +42,28 @@ public interface ZoneApiV2 { Response proxyGet( @PathParam("environment") String env, @PathParam("region") String region, - @PathParam("proxy_request") String proxyRequest, - @Context HttpServletRequest request); + @PathParam("proxy_request") String proxyRequest); @POST @Path("/{environment}/{region}/{proxy_request: .+}") Response proxyPost( @PathParam("environment") String env, @PathParam("region") String region, - @PathParam("proxy_request") String proxyRequest, - @Context HttpServletRequest request); + @PathParam("proxy_request") String proxyRequest); @PUT @Path("/{environment}/{region}/{proxy_request: .+}") Response proxyPut( @PathParam("environment") String env, @PathParam("region") String region, - @PathParam("proxy_request") String proxyRequest, - @Context HttpServletRequest request); + @PathParam("proxy_request") String proxyRequest); @DELETE @Path("/{environment}/{region}/{proxy_request: .+}") Response proxyDelete( @PathParam("environment") String env, @PathParam("region") String region, - @PathParam("proxy_request") String proxyRequest, - @Context HttpServletRequest request); + @PathParam("proxy_request") String proxyRequest); // Explicit mappings of some proxy requests (to enable creation of proxy clients with javax.ws.rs) diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java index 7e4d543fd18..aa3d1be879e 100644 --- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java +++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.identifiers; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -147,7 +147,7 @@ public class IdentifierTest { @Test public void application_instance_id_dotted_string_is_subindentifers_concatinated_with_dots() { DeploymentId id = new DeploymentId(com.yahoo.config.provision.ApplicationId.from("tenant", "application", "instance"), - new Zone(Environment.prod, RegionName.from("region"))); + ZoneId.from("prod", "region")); assertEquals("tenant.application.prod.region.instance", id.dottedString()); } } diff --git a/controller-server/pom.xml b/controller-server/pom.xml index 0cfcbc40601..b033286b82a 100644 --- a/controller-server/pom.xml +++ b/controller-server/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>controller-server</artifactId> <packaging>container-plugin</packaging> diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java index e3400d76bce..ae2de96f511 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java @@ -7,14 +7,16 @@ import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; +import com.yahoo.vespa.hosted.controller.application.ApplicationRotation; import com.yahoo.vespa.hosted.controller.application.ApplicationRevision; import com.yahoo.vespa.hosted.controller.application.Change; import com.yahoo.vespa.hosted.controller.application.Change.VersionChange; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; import java.time.Instant; import java.util.Collections; @@ -38,38 +40,44 @@ public class Application { private final ApplicationId id; private final DeploymentSpec deploymentSpec; private final ValidationOverrides validationOverrides; - private final Map<Zone, Deployment> deployments; + private final Map<ZoneId, Deployment> deployments; private final DeploymentJobs deploymentJobs; private final Optional<Change> deploying; private final boolean outstandingChange; private final Optional<IssueId> ownershipIssueId; private final ApplicationMetrics metrics; + private final Optional<RotationId> rotation; /** Creates an empty application */ public Application(ApplicationId id) { - this(id, DeploymentSpec.empty, ValidationOverrides.empty, ImmutableMap.of(), + this(id, DeploymentSpec.empty, ValidationOverrides.empty, Collections.emptyMap(), new DeploymentJobs(Optional.empty(), Collections.emptyList(), Optional.empty()), - Optional.empty(), false, Optional.empty(), new ApplicationMetrics(0, 0)); + Optional.empty(), false, Optional.empty(), new ApplicationMetrics(0, 0), + Optional.empty()); } /** Used from persistence layer: Do not use */ public Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides, List<Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying, - boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics) { + boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics, + Optional<RotationId> rotation) { this(id, deploymentSpec, validationOverrides, deployments.stream().collect(Collectors.toMap(Deployment::zone, d -> d)), - deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics); + deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics, rotation); } Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides, - Map<Zone, Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying, - boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics) { + Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying, + boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics, + Optional<RotationId> rotation) { Objects.requireNonNull(id, "id cannot be null"); Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null"); Objects.requireNonNull(validationOverrides, "validationOverrides cannot be null"); Objects.requireNonNull(deployments, "deployments cannot be null"); Objects.requireNonNull(deploymentJobs, "deploymentJobs cannot be null"); Objects.requireNonNull(deploying, "deploying cannot be null"); + Objects.requireNonNull(metrics, "metrics cannot be null"); + Objects.requireNonNull(rotation, "rotation cannot be null"); this.id = id; this.deploymentSpec = deploymentSpec; this.validationOverrides = validationOverrides; @@ -79,6 +87,7 @@ public class Application { this.outstandingChange = outstandingChange; this.ownershipIssueId = ownershipIssueId; this.metrics = metrics; + this.rotation = rotation; } public ApplicationId id() { return id; } @@ -97,13 +106,13 @@ public class Application { public ValidationOverrides validationOverrides() { return validationOverrides; } /** Returns an immutable map of the current deployments of this */ - public Map<Zone, Deployment> deployments() { return deployments; } + public Map<ZoneId, Deployment> deployments() { return deployments; } /** * Returns an immutable map of the current *production* deployments of this * (deployments also includes manually deployed environments) */ - public Map<Zone, Deployment> productionDeployments() { + public Map<ZoneId, Deployment> productionDeployments() { return ImmutableMap.copyOf(deployments.values().stream() .filter(deployment -> deployment.zone().environment() == Environment.prod) .collect(Collectors.toMap(Deployment::zone, Function.identity()))); @@ -142,7 +151,7 @@ public class Application { } /** Returns the version a new deployment to this zone should use for this application */ - public Version deployVersionIn(Zone zone, Controller controller) { + public Version deployVersionIn(ZoneId zone, Controller controller) { if (deploying().isPresent() && deploying().get() instanceof VersionChange) return ((Change.VersionChange) deploying().get()).version(); @@ -150,13 +159,13 @@ public class Application { } /** Returns the current version this application has, or if none; should use, in the given zone */ - public Version versionIn(Zone zone, Controller controller) { + public Version versionIn(ZoneId zone, Controller controller) { return Optional.ofNullable(deployments().get(zone)).map(Deployment::version) // Already deployed in this zone: Use that version .orElse(oldestDeployedVersion().orElse(controller.systemVersion())); } /** Returns the revision a new deployment to this zone should use for this application, or empty if we don't know */ - public Optional<ApplicationRevision> deployRevisionIn(Zone zone) { + public Optional<ApplicationRevision> deployRevisionIn(ZoneId zone) { if (deploying().isPresent() && deploying().get() instanceof Change.ApplicationChange) return ((Change.ApplicationChange) deploying().get()).revision(); @@ -164,10 +173,15 @@ public class Application { } /** Returns the revision this application is or should be deployed with in the given zone, or empty if unknown. */ - public Optional<ApplicationRevision> revisionIn(Zone zone) { + public Optional<ApplicationRevision> revisionIn(ZoneId zone) { return Optional.ofNullable(deployments().get(zone)).map(Deployment::revision); } + /** Returns the global rotation of this, if present */ + public Optional<ApplicationRotation> rotation() { + return rotation.map(rotation -> new ApplicationRotation(id, rotation)); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 841b9b4dd9f..ec1051a3674 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -1,17 +1,15 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller; -import com.google.common.collect.ImmutableSet; import com.yahoo.component.Version; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.TenantName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.api.ActivateResult; -import com.yahoo.vespa.hosted.controller.api.ApplicationAlias; import com.yahoo.vespa.hosted.controller.api.InstanceEndpoints; import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; @@ -23,33 +21,39 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.identifiers.RevisionId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log; import com.yahoo.vespa.hosted.controller.api.integration.configserver.NoInstanceException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.PrepareResponse; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; +import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData; import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordId; +import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; -import com.yahoo.vespa.hosted.controller.api.rotation.Rotation; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.ApplicationRevision; +import com.yahoo.vespa.hosted.controller.application.ApplicationRotation; import com.yahoo.vespa.hosted.controller.application.Change; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger; import com.yahoo.vespa.hosted.controller.maintenance.DeploymentExpirer; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; -import com.yahoo.vespa.hosted.rotation.RotationRepository; +import com.yahoo.vespa.hosted.controller.rotation.Rotation; +import com.yahoo.vespa.hosted.controller.rotation.RotationLock; +import com.yahoo.vespa.hosted.controller.rotation.RotationRepository; +import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; +import com.yahoo.yolean.Exceptions; import java.io.IOException; import java.net.URI; @@ -82,6 +86,7 @@ public class ApplicationController { /** For permanent storage */ private final ControllerDb db; + /** For working memory storage and sharing between controllers */ private final CuratorDb curator; @@ -93,26 +98,26 @@ public class ApplicationController { private final Clock clock; private final DeploymentTrigger deploymentTrigger; - + ApplicationController(Controller controller, ControllerDb db, CuratorDb curator, - RotationRepository rotationRepository, - AthenzClientFactory zmsClientFactory, + AthenzClientFactory zmsClientFactory, RotationsConfig rotationsConfig, NameService nameService, ConfigServerClient configserverClient, RoutingGenerator routingGenerator, Clock clock) { this.controller = controller; this.db = db; this.curator = curator; - this.rotationRepository = rotationRepository; this.zmsClientFactory = zmsClientFactory; this.nameService = nameService; this.configserverClient = configserverClient; this.routingGenerator = routingGenerator; this.clock = clock; + this.rotationRepository = new RotationRepository(rotationsConfig, this, curator); this.deploymentTrigger = new DeploymentTrigger(controller, curator, clock); - for (Application application : db.listApplications()) - lockedIfPresent(application.id(), this::store); + for (Application application : db.listApplications()) { + lockIfPresent(application.id(), this::store); + } } /** Returns the application with the given id, or null if it is not present */ @@ -231,10 +236,6 @@ public class ApplicationController { if ( ! (id.instance().value().equals("default") || id.instance().value().startsWith("default-pr"))) // TODO: Support instances properly throw new UnsupportedOperationException("Only the instance names 'default' and names starting with 'default-pr' are supported at the moment"); try (Lock lock = lock(id)) { - // TODO: Throwing is duplicated below. - if (get(id).isPresent()) - throw new IllegalArgumentException("An application with id '" + id + "' already exists"); - com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId.validate(id.application().value()); Optional<Tenant> tenant = controller.tenants().tenant(new TenantId(id.tenant().value())); @@ -266,13 +267,14 @@ public class ApplicationController { /** Deploys an application. If the application does not exist it is created. */ // TODO: Get rid of the options arg - public ActivateResult deployApplication(ApplicationId applicationId, Zone zone, + public ActivateResult deployApplication(ApplicationId applicationId, ZoneId zone, ApplicationPackage applicationPackage, DeployOptions options) { try (Lock lock = lock(applicationId)) { - // TODO: Shouldn't this go through the above method? Seems you can cheat the checks here ... ? - LockedApplication application = get(applicationId).map(application1 -> new LockedApplication(application1, lock)).orElse(new LockedApplication( - new Application(applicationId), lock) - ); + // Not ideal, but since we create on missing and return a result computed inside the lock, + // the lock-with-action methods cannot be used + LockedApplication application = get(applicationId) + .map(app -> new LockedApplication(app, lock)) + .orElseGet(() -> new LockedApplication(new Application(applicationId), lock)); // Determine what we are doing Version version; @@ -328,15 +330,35 @@ public class ApplicationController { throw new IllegalArgumentException("Rejecting deployment of " + application + " to " + zone + " as the requested version " + version + " is older than" + " the current version " + existingDeployment.version()); - } + } + + Optional<Rotation> rotation; + try (RotationLock rotationLock = rotationRepository.lock()) { + rotation = getRotation(application, zone, rotationLock); + if (rotation.isPresent()) { + application = application.with(rotation.get().id()); + store(application); // store assigned rotation even if deployment fails + registerRotationInDns(rotation.get(), application.rotation().get().dnsName()); + } + } + + // TODO: Improve config server client interface and simplify + Set<String> cnames = application.rotation() + .map(ApplicationRotation::dnsName) + .map(Collections::singleton) + .orElseGet(Collections::emptySet); + + Set<com.yahoo.vespa.hosted.controller.api.rotation.Rotation> rotations = rotation + .map(r -> new com.yahoo.vespa.hosted.controller.api.rotation.Rotation( + new com.yahoo.vespa.hosted.controller.api.identifiers.RotationId( + r.id().asString()), r.name())) + .map(Collections::singleton) + .orElseGet(Collections::emptySet); // Carry out deployment - DeploymentId deploymentId = new DeploymentId(applicationId, zone); - ApplicationRotation rotationInDns = registerRotationInDns(deploymentId, getOrAssignRotation(deploymentId, - applicationPackage)); - options = withVersion(version, options); + options = withVersion(version, options); ConfigServerClient.PreparedApplication preparedApplication = - configserverClient.prepare(deploymentId, options, rotationInDns.cnames(), rotationInDns.rotations(), + configserverClient.prepare(new DeploymentId(applicationId, zone), options, cnames, rotations, applicationPackage.zippedContent()); preparedApplication.activate(); application = application.withNewDeployment(zone, revision, version, clock.instant()); @@ -347,7 +369,7 @@ public class ApplicationController { } } - private ActivateResult unexpectedDeployment(ApplicationId applicationId, Zone zone, ApplicationPackage applicationPackage) { + private ActivateResult unexpectedDeployment(ApplicationId applicationId, ZoneId zone, ApplicationPackage applicationPackage) { Log logEntry = new Log(); logEntry.level = "WARNING"; logEntry.time = clock.instant().toEpochMilli(); @@ -384,9 +406,9 @@ public class ApplicationController { private LockedApplication deleteUnreferencedDeploymentJobs(LockedApplication application) { for (DeploymentJobs.JobType job : application.deploymentJobs().jobStatus().keySet()) { - Optional<Zone> zone = job.zone(controller.system()); + Optional<ZoneId> zone = job.zone(controller.system()); - if ( ! job.isProduction() || (zone.isPresent() && application.deploymentSpec().includes(zone.get().environment(), zone.map(Zone::region)))) + if ( ! job.isProduction() || (zone.isPresent() && application.deploymentSpec().includes(zone.get().environment(), zone.map(ZoneId::region)))) continue; application = application.withoutDeploymentJob(job); } @@ -430,35 +452,35 @@ public class ApplicationController { gitRevision.commit.id())); } - private ApplicationRotation registerRotationInDns(DeploymentId deploymentId, ApplicationRotation applicationRotation) { - ApplicationAlias alias = new ApplicationAlias(deploymentId.applicationId()); - if (applicationRotation.rotations().isEmpty()) return applicationRotation; - - Rotation rotation = applicationRotation.rotations().iterator().next(); // at this time there should be only one rotation assigned - String endpointName = alias.toString(); + /** Register a DNS name for rotation */ + private void registerRotationInDns(Rotation rotation, String dnsName) { try { - Optional<Record> record = nameService.findRecord(Record.Type.CNAME, endpointName); - if (!record.isPresent()) { - RecordId recordId = nameService.createCname(endpointName, rotation.rotationName); - log.info("Registered mapping with record ID " + recordId.id() + ": " + - endpointName + " -> " + rotation.rotationName); + Optional<Record> record = nameService.findRecord(Record.Type.CNAME, RecordName.from(dnsName)); + RecordData rotationName = RecordData.fqdn(rotation.name()); + if (record.isPresent()) { + // Ensure that the existing record points to the correct rotation + if (!record.get().data().equals(rotationName)) { + nameService.updateRecord(record.get().id(), rotationName); + log.info("Updated mapping for record ID " + record.get().id().asString() + ": '" + dnsName + + "' -> '" + rotation.name() + "'"); + } + } else { + RecordId id = nameService.createCname(RecordName.from(dnsName), rotationName); + log.info("Registered mapping with record ID " + id.asString() + ": '" + dnsName + "' -> '" + + rotation.name() + "'"); } - } - catch (RuntimeException e) { + } catch (RuntimeException e) { log.log(Level.WARNING, "Failed to register CNAME", e); } - return new ApplicationRotation(Collections.singleton(endpointName), Collections.singleton(rotation)); } - private ApplicationRotation getOrAssignRotation(DeploymentId deploymentId, ApplicationPackage applicationPackage) { - if (deploymentId.zone().environment().equals(Environment.prod)) { - return new ApplicationRotation(Collections.emptySet(), - rotationRepository.getOrAssignRotation(deploymentId.applicationId(), - applicationPackage.deploymentSpec())); - } else { - return new ApplicationRotation(Collections.emptySet(), - Collections.emptySet()); + /** Get an available rotation, if deploying to a production zone and a service ID is specified */ + private Optional<Rotation> getRotation(Application application, ZoneId zone, RotationLock lock) { + if (zone.environment() != Environment.prod || + !application.deploymentSpec().globalServiceId().isPresent()) { + return Optional.empty(); } + return Optional.of(rotationRepository.getRotation(application, lock)); } /** Returns the endpoints of the deployment, or empty if obtaining them failed */ @@ -476,37 +498,50 @@ public class ApplicationController { return Optional.of(new InstanceEndpoints(endPointUrls)); } catch (RuntimeException e) { - log.log(Level.FINE, "Failed to get endpoint information for " + deploymentId, e); + log.log(Level.WARNING, "Failed to get endpoint information for " + deploymentId + ": " + + Exceptions.toMessageString(e)); return Optional.empty(); } } /** - * Deletes the application with this id + * Deletes the the given application. All known instances of the applications will be deleted, + * including PR instances. * * @throws IllegalArgumentException if the application has deployments or the caller is not authorized - * @throws NotExistsException if the application does not exist + * @throws NotExistsException if no instances of the application exist */ - public void deleteApplication(ApplicationId id, Optional<NToken> token) { - if ( ! controller.applications().get(id).isPresent()) - throw new NotExistsException("Could not delete application '" + id + "': Application not found"); + public void deleteApplication(ApplicationId applicationId, Optional<NToken> token) { + // Find all instances of the application + List<ApplicationId> instances = controller.applications().asList(applicationId.tenant()) + .stream() + .map(Application::id) + .filter(id -> id.application().equals(applicationId.application()) && + id.tenant().equals(applicationId.tenant())) + .collect(Collectors.toList()); + if (instances.isEmpty()) { + throw new NotExistsException("Could not delete application '" + applicationId + "': Application not found"); + } - lockedOrThrow(id, application -> { + // TODO: Make this one transaction when database is moved to ZooKeeper + instances.forEach(id -> lockOrThrow(id, application -> { if ( ! application.deployments().isEmpty()) throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments"); - + Tenant tenant = controller.tenants().tenant(new TenantId(id.tenant().value())).get(); if (tenant.isAthensTenant() && ! token.isPresent()) throw new IllegalArgumentException("Could not delete '" + application + "': No NToken provided"); - // NB: Next 2 lines should have been one transaction - if (tenant.isAthensTenant()) + // Only delete in Athenz once + if (id.instance().isDefault() && tenant.isAthensTenant()) { zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get()) - .deleteApplication(tenant.getAthensDomain().get(), new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); + .deleteApplication(tenant.getAthensDomain().get(), + new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); + } db.deleteApplication(id); log.info("Deleted " + application); - }); + })); } /** @@ -521,24 +556,25 @@ public class ApplicationController { /** * Acquire a locked application to modify and store, if there is an application with the given id. * - * @param applicationId Id of the application to lock and get. - * @param actions Things to do with the locked application. + * @param applicationId ID of the application to lock and get. + * @param action Function which acts on the locked application. */ - public void lockedIfPresent(ApplicationId applicationId, Consumer<LockedApplication> actions) { + public void lockIfPresent(ApplicationId applicationId, Consumer<LockedApplication> action) { try (Lock lock = lock(applicationId)) { - get(applicationId).map(application -> new LockedApplication(application, lock)).ifPresent(actions); + get(applicationId).map(application -> new LockedApplication(application, lock)).ifPresent(action); } } /** * Acquire a locked application to modify and store, or throw an exception if no application has the given id. * - * @param applicationId Id of the application to lock and require. - * @param actions Things to do with the locked application. + * @param applicationId ID of the application to lock and require. + * @param action Function which acts on the locked application. + * @throws IllegalArgumentException when application does not exist. */ - public void lockedOrThrow(ApplicationId applicationId, Consumer<LockedApplication> actions) { + public void lockOrThrow(ApplicationId applicationId, Consumer<LockedApplication> action) { try (Lock lock = lock(applicationId)) { - actions.accept(new LockedApplication(require(applicationId), lock)); + action.accept(new LockedApplication(require(applicationId), lock)); } } @@ -551,18 +587,14 @@ public class ApplicationController { deploymentTrigger.triggerFromCompletion(report); } - // TODO: Collapse this method and the next - public void restart(DeploymentId deploymentId) { - try { - configserverClient.restart(deploymentId, Optional.empty()); - } - catch (NoInstanceException e) { - throw new IllegalArgumentException("Could not restart " + deploymentId + ": No such deployment"); - } - } - public void restartHost(DeploymentId deploymentId, Hostname hostname) { + /** + * Tells config server to schedule a restart of all nodes in this deployment + * + * @param hostname If non-empty, restart will only be scheduled for this host + */ + public void restart(DeploymentId deploymentId, Optional<Hostname> hostname) { try { - configserverClient.restart(deploymentId, Optional.of(hostname)); + configserverClient.restart(deploymentId, hostname); } catch (NoInstanceException e) { throw new IllegalArgumentException("Could not restart " + deploymentId + ": No such deployment"); @@ -570,7 +602,7 @@ public class ApplicationController { } /** Deactivate application in the given zone */ - public void deactivate(Application application, Zone zone) { + public void deactivate(Application application, ZoneId zone) { deactivate(application, zone, Optional.empty(), false); } @@ -579,13 +611,13 @@ public class ApplicationController { deactivate(application, deployment.zone(), Optional.of(deployment), requireThatDeploymentHasExpired); } - private void deactivate(Application application, Zone zone, Optional<Deployment> deployment, + private void deactivate(Application application, ZoneId zone, Optional<Deployment> deployment, boolean requireThatDeploymentHasExpired) { if (requireThatDeploymentHasExpired && deployment.isPresent() && ! DeploymentExpirer.hasExpired(controller.zoneRegistry(), deployment.get(), clock.instant())) return; - lockedOrThrow(application.id(), lockedApplication -> + lockOrThrow(application.id(), lockedApplication -> store(deactivate(lockedApplication, zone))); } @@ -594,7 +626,7 @@ public class ApplicationController { * * @return the application with the deployment in the given zone removed */ - private LockedApplication deactivate(LockedApplication application, Zone zone) { + private LockedApplication deactivate(LockedApplication application, ZoneId zone) { try { configserverClient.deactivate(new DeploymentId(application.id(), zone)); } @@ -624,7 +656,7 @@ public class ApplicationController { } /** Returns whether a direct deployment to given zone is allowed */ - private static boolean canDeployDirectlyTo(Zone zone, DeployOptions options) { + private static boolean canDeployDirectlyTo(ZoneId zone, DeployOptions options) { return ! options.screwdriverBuildJob.isPresent() || options.screwdriverBuildJob.get().screwdriverId == null || zone.environment().isManuallyDeployed(); @@ -635,25 +667,13 @@ public class ApplicationController { deploymentSpec.zones().stream() .filter(zone -> zone.environment() == Environment.prod) .forEach(zone -> { - if ( ! controller.zoneRegistry().getZone(zone.environment(), zone.region().orElse(null)).isPresent()) + if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(), zone.region().orElse(null)))) throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!"); }); } - - private static final class ApplicationRotation { - - private final ImmutableSet<String> cnames; - private final ImmutableSet<Rotation> rotations; - - public ApplicationRotation(Set<String> cnames, Set<Rotation> rotations) { - this.cnames = ImmutableSet.copyOf(cnames); - this.rotations = ImmutableSet.copyOf(rotations); - } - - public Set<String> cnames() { return cnames; } - public Set<Rotation> rotations() { return rotations; } - + public RotationRepository rotationRepository() { + return rotationRepository; } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index b854ad3f771..f50958f0e66 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -6,10 +6,8 @@ import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; import com.yahoo.component.Version; import com.yahoo.component.Vtag; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; @@ -25,12 +23,12 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingSe import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; -import com.yahoo.vespa.hosted.rotation.RotationRepository; +import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import com.yahoo.vespa.serviceview.bindings.ApplicationView; import java.net.URI; @@ -39,7 +37,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.logging.Logger; /** @@ -64,7 +61,6 @@ public class Controller extends AbstractComponent { private final ApplicationController applicationController; private final TenantController tenantController; private final Clock clock; - private final RotationRepository rotationRepository; private final GitHub gitHub; private final EntityService entityService; private final GlobalRoutingService globalRoutingService; @@ -82,19 +78,19 @@ public class Controller extends AbstractComponent { * @param curator the curator instance storing working state shared between controller instances */ @Inject - public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository, + public Controller(ControllerDb db, CuratorDb curator, RotationsConfig rotationsConfig, GitHub gitHub, EntityService entityService, Organization organization, GlobalRoutingService globalRoutingService, ZoneRegistry zoneRegistry, ConfigServerClient configServerClient, MetricsService metricsService, NameService nameService, RoutingGenerator routingGenerator, Chef chefClient, AthenzClientFactory athenzClientFactory) { - this(db, curator, rotationRepository, + this(db, curator, rotationsConfig, gitHub, entityService, organization, globalRoutingService, zoneRegistry, configServerClient, metricsService, nameService, routingGenerator, chefClient, Clock.systemUTC(), athenzClientFactory); } - public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository, + public Controller(ControllerDb db, CuratorDb curator, RotationsConfig rotationsConfig, GitHub gitHub, EntityService entityService, Organization organization, GlobalRoutingService globalRoutingService, ZoneRegistry zoneRegistry, ConfigServerClient configServerClient, @@ -103,7 +99,7 @@ public class Controller extends AbstractComponent { AthenzClientFactory athenzClientFactory) { Objects.requireNonNull(db, "Controller db cannot be null"); Objects.requireNonNull(curator, "Curator cannot be null"); - Objects.requireNonNull(rotationRepository, "Rotation repository cannot be null"); + Objects.requireNonNull(rotationsConfig, "RotationsConfig cannot be null"); Objects.requireNonNull(gitHub, "GitHubClient cannot be null"); Objects.requireNonNull(entityService, "EntityService cannot be null"); Objects.requireNonNull(organization, "Organization cannot be null"); @@ -117,7 +113,6 @@ public class Controller extends AbstractComponent { Objects.requireNonNull(clock, "Clock cannot be null"); Objects.requireNonNull(athenzClientFactory, "Athens cannot be null"); - this.rotationRepository = rotationRepository; this.curator = curator; this.gitHub = gitHub; this.entityService = entityService; @@ -130,7 +125,8 @@ public class Controller extends AbstractComponent { this.clock = clock; this.athenzClientFactory = athenzClientFactory; - applicationController = new ApplicationController(this, db, curator, rotationRepository, athenzClientFactory, + applicationController = new ApplicationController(this, db, curator, athenzClientFactory, + rotationsConfig, nameService, configServerClient, routingGenerator, clock); tenantController = new TenantController(this, db, curator, entityService, athenzClientFactory); } @@ -156,35 +152,16 @@ public class Controller extends AbstractComponent { public Clock clock() { return clock; } - public URI getElkUri(DeploymentId deploymentId) { - return elkUrl(zoneRegistry.getLogServerUri(deploymentId.zone().environment(), deploymentId.zone().region()), deploymentId); + public Optional<URI> getLogServerUrl(DeploymentId deploymentId) { + return zoneRegistry.getLogServerUri(deploymentId); } - public List<URI> getConfigServerUris(Environment environment, RegionName region) { - return zoneRegistry.getConfigServerUris(environment, region); + public List<URI> getConfigServerUris(ZoneId zoneId) { + return zoneRegistry.getConfigServerUris(zoneId); } public ZoneRegistry zoneRegistry() { return zoneRegistry; } - private URI elkUrl(Optional<URI> kibanaHost, DeploymentId deploymentId) { - String kibanaQuery = "/#/discover?_g=()&_a=(columns:!(_source)," + - "index:'logstash-*',interval:auto," + - "query:(query_string:(analyze_wildcard:!t,query:'" + - "HV-tenant:%22" + deploymentId.applicationId().tenant().value() + "%22%20" + - "AND%20HV-application:%22" + deploymentId.applicationId().application().value() + "%22%20" + - "AND%20HV-region:%22" + deploymentId.zone().region().value() + "%22%20" + - "AND%20HV-instance:%22" + deploymentId.applicationId().instance().value() + "%22%20" + - "AND%20HV-environment:%22" + deploymentId.zone().environment().value() + "%22'))," + - "sort:!('@timestamp',desc))"; - - URI kibanaPath = URI.create(kibanaQuery); - return kibanaHost.map(uri -> uri.resolve(kibanaPath)).orElse(null); - } - - public Set<URI> getRotationUris(ApplicationId id) { - return rotationRepository.getRotationUris(id); - } - public Map<String, RotationStatus> getHealthStatus(String hostname) { return globalRoutingService.getHealthStatus(hostname); } @@ -209,7 +186,9 @@ public class Controller extends AbstractComponent { return configServerClient.grabLog(deploymentId); } - public GitHub gitHub() { return gitHub; } + public GitHub gitHub() { + return gitHub; + } /** Replace the current version status by a new one */ public void updateVersionStatus(VersionStatus newStatus) { @@ -232,7 +211,9 @@ public class Controller extends AbstractComponent { .orElse(Vtag.currentVersion); } - public MetricsService metricsService() { return metricsService; } + public MetricsService metricsService() { + return metricsService; + } public SystemName system() { return zoneRegistry.system(); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java index e8c8f8a389c..72ed1a42435 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java @@ -6,10 +6,12 @@ import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; +import com.yahoo.vespa.hosted.controller.application.ApplicationRotation; import com.yahoo.vespa.hosted.controller.application.ApplicationRevision; import com.yahoo.vespa.hosted.controller.application.Change; import com.yahoo.vespa.hosted.controller.application.ClusterInfo; @@ -18,7 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; - +import com.yahoo.vespa.hosted.controller.rotation.RotationId; import java.time.Instant; import java.util.LinkedHashMap; @@ -34,12 +36,6 @@ import java.util.Optional; */ public class LockedApplication extends Application { - private LockedApplication(Builder builder) { - super(builder.applicationId, builder.deploymentSpec, builder.validationOverrides, - builder.deployments, builder.deploymentJobs, builder.deploying, - builder.hasOutstandingChange, builder.ownershipIssueId, builder.metrics); - } - /** * Used to create a locked application * @@ -50,6 +46,12 @@ public class LockedApplication extends Application { this(new Builder(application)); } + private LockedApplication(Builder builder) { + super(builder.applicationId, builder.deploymentSpec, builder.validationOverrides, + builder.deployments, builder.deploymentJobs, builder.deploying, + builder.hasOutstandingChange, builder.ownershipIssueId, builder.metrics, builder.rotation); + } + public LockedApplication withProjectId(long projectId) { return new LockedApplication(new Builder(this).with(deploymentJobs().withProjectId(projectId))); } @@ -67,7 +69,7 @@ public class LockedApplication extends Application { return new LockedApplication(new Builder(this).with(deploymentJobs().withTriggering(type, change, version, revision, reason, triggerTime))); } - public LockedApplication withNewDeployment(Zone zone, ApplicationRevision revision, Version version, Instant instant) { + public LockedApplication withNewDeployment(ZoneId zone, ApplicationRevision revision, Version version, Instant instant) { // Use info from previous deployment if available, otherwise create a new one. Deployment previousDeployment = deployments().getOrDefault(zone, new Deployment(zone, revision, version, instant)); Deployment newDeployment = new Deployment(zone, revision, version, instant, @@ -77,27 +79,27 @@ public class LockedApplication extends Application { return with(newDeployment); } - public LockedApplication withClusterUtilization(Zone zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) { + public LockedApplication withClusterUtilization(ZoneId zone, Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization) { Deployment deployment = deployments().get(zone); if (deployment == null) return this; // No longer deployed in this zone. return with(deployment.withClusterUtils(clusterUtilization)); } - public LockedApplication withClusterInfo(Zone zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) { + public LockedApplication withClusterInfo(ZoneId zone, Map<ClusterSpec.Id, ClusterInfo> clusterInfo) { Deployment deployment = deployments().get(zone); if (deployment == null) return this; // No longer deployed in this zone. return with(deployment.withClusterInfo(clusterInfo)); } - public LockedApplication with(Zone zone, DeploymentMetrics deploymentMetrics) { + public LockedApplication with(ZoneId zone, DeploymentMetrics deploymentMetrics) { Deployment deployment = deployments().get(zone); if (deployment == null) return this; // No longer deployed in this zone. return with(deployment.withMetrics(deploymentMetrics)); } - public LockedApplication withoutDeploymentIn(Zone zone) { - Map<Zone, Deployment> deployments = new LinkedHashMap<>(deployments()); + public LockedApplication withoutDeploymentIn(ZoneId zone) { + Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(deployments()); deployments.remove(zone); return new LockedApplication(new Builder(this).with(deployments)); } @@ -130,6 +132,10 @@ public class LockedApplication extends Application { return new LockedApplication(new Builder(this).with(metrics)); } + public LockedApplication with(RotationId rotation) { + return new LockedApplication(new Builder(this).with(rotation)); + } + public Version deployVersionFor(DeploymentJobs.JobType jobType, Controller controller) { return jobType == JobType.component ? controller.systemVersion() @@ -144,7 +150,7 @@ public class LockedApplication extends Application { /** Don't expose non-leaf sub-objects. */ private LockedApplication with(Deployment deployment) { - Map<Zone, Deployment> deployments = new LinkedHashMap<>(deployments()); + Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(deployments()); deployments.put(deployment.zone(), deployment); return new LockedApplication(new Builder(this).with(deployments)); } @@ -155,12 +161,13 @@ public class LockedApplication extends Application { private final ApplicationId applicationId; private DeploymentSpec deploymentSpec; private ValidationOverrides validationOverrides; - private Map<Zone, Deployment> deployments; + private Map<ZoneId, Deployment> deployments; private DeploymentJobs deploymentJobs; private Optional<Change> deploying; private boolean hasOutstandingChange; private Optional<IssueId> ownershipIssueId; private ApplicationMetrics metrics; + private Optional<RotationId> rotation; private Builder(Application application) { this.applicationId = application.id(); @@ -172,16 +179,53 @@ public class LockedApplication extends Application { this.hasOutstandingChange = application.hasOutstandingChange(); this.ownershipIssueId = application.ownershipIssueId(); this.metrics = application.metrics(); + this.rotation = application.rotation().map(ApplicationRotation::id); + } + + private Builder with(DeploymentSpec deploymentSpec) { + this.deploymentSpec = deploymentSpec; + return this; } - private Builder with(DeploymentSpec deploymentSpec) { this.deploymentSpec = deploymentSpec; return this; } - private Builder with(ValidationOverrides validationOverrides) { this.validationOverrides = validationOverrides; return this; } - private Builder with(Map<Zone, Deployment> deployments) { this.deployments = deployments; return this; } - private Builder with(DeploymentJobs deploymentJobs) { this.deploymentJobs = deploymentJobs; return this; } - private Builder withDeploying(Optional<Change> deploying) { this.deploying = deploying; return this; } - private Builder with(boolean hasOutstandingChange) { this.hasOutstandingChange = hasOutstandingChange; return this; } - private Builder withOwnershipIssueId(Optional<IssueId> ownershipIssueId) { this.ownershipIssueId = ownershipIssueId; return this; } - private Builder with(ApplicationMetrics metrics) { this.metrics = metrics; return this; } + private Builder with(ValidationOverrides validationOverrides) { + this.validationOverrides = validationOverrides; + return this; + } + + private Builder with(Map<ZoneId, Deployment> deployments) { + this.deployments = deployments; + return this; + } + + private Builder with(DeploymentJobs deploymentJobs) { + this.deploymentJobs = deploymentJobs; + return this; + } + + private Builder withDeploying(Optional<Change> deploying) { + this.deploying = deploying; + return this; + } + + private Builder with(boolean hasOutstandingChange) { + this.hasOutstandingChange = hasOutstandingChange; + return this; + } + + private Builder withOwnershipIssueId(Optional<IssueId> ownershipIssueId) { + this.ownershipIssueId = ownershipIssueId; + return this; + } + + private Builder with(ApplicationMetrics metrics) { + this.metrics = metrics; + return this; + } + + private Builder with(RotationId rotation) { + this.rotation = Optional.of(rotation); + return this; + } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java index 9530e9a982c..a52098a4a0f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java @@ -12,11 +12,11 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.persistence.PersistenceException; @@ -67,7 +67,7 @@ public class TenantController { public List<Tenant> asList(UserId user) { Set<UserGroup> userGroups = entityService.getUserGroups(user); Set<AthenzDomain> userDomains = new HashSet<>(athenzClientFactory.createZtsClientWithServicePrincipal() - .getTenantDomainsForUser(AthenzUtils.createPrincipal(user))); + .getTenantDomainsForUser(AthenzUser.fromUserId(user))); Predicate<Tenant> hasUsersGroup = (tenant) -> tenant.getUserGroup().isPresent() && userGroups.contains(tenant.getUserGroup().get()); Predicate<Tenant> hasUsersDomain = (tenant) -> tenant.getAthensDomain().isPresent() && userDomains.contains(tenant.getAthensDomain().get()); @@ -200,8 +200,7 @@ public class TenantController { try (Lock lock = lock(tenantId)) { Tenant existing = tenant(tenantId).orElseThrow(() -> new NotExistsException(tenantId)); if (existing.isAthensTenant()) return existing; // nothing to do - log.info("Starting migration of " + existing + " to Athenz domain " + tenantDomain.id() + - " using " + nToken.getPrincipal()); + log.info("Starting migration of " + existing + " to Athenz domain " + tenantDomain.id()); if (tenantHaving(tenantDomain).isPresent()) throw new IllegalArgumentException("Could not migrate " + existing + " to " + tenantDomain + ": " + "This domain is already used by " + tenantHaving(tenantDomain).get()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java deleted file mode 100644 index a9e144a3227..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/api/ApplicationAlias.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api; - -import com.yahoo.config.provision.ApplicationId; - -import java.net.URI; -import java.net.URISyntaxException; - -/** - * A DNS alias for an application endpoint. - * - * @author smorgrav - */ -public class ApplicationAlias { - - private static final String dnsSuffix = "global.vespa.yahooapis.com"; - - private final ApplicationId applicationId; - - public ApplicationAlias(ApplicationId applicationId) { - this.applicationId = applicationId; - } - - @Override - public String toString() { - return String.format("%s.%s.%s", - toDns(applicationId.application().value()), - toDns(applicationId.tenant().value()), - dnsSuffix); - } - - private String toDns(String id) { - return id.replace('_', '-'); - } - - public URI toHttpUri() { - try { - return new URI("http://" + this + ":4080/"); - } catch(URISyntaxException use) { - throw new RuntimeException("Illegal URI syntax"); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ApplicationAlias that = (ApplicationAlias) o; - - return applicationId.equals(that.applicationId); - } - - @Override - public int hashCode() { return applicationId.hashCode(); } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java new file mode 100644 index 00000000000..e4aed04a01c --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationRotation.java @@ -0,0 +1,51 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.application; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; + +import java.net.URI; + +/** + * Represents an application's global rotation. + * + * @author mpolden + */ +public class ApplicationRotation { + + public static final String DNS_SUFFIX = "global.vespa.yahooapis.com"; + private static final int port = 4080; + + private final URI url; + private final RotationId id; + + public ApplicationRotation(ApplicationId application, RotationId id) { + this.url = URI.create(String.format("http://%s.%s.%s:%d/", + sanitize(application.application().value()), + sanitize(application.tenant().value()), + DNS_SUFFIX, + port)); + this.id = id; + } + + /** ID of the rotation */ + public RotationId id() { + return id; + } + + /** URL to this rotation */ + public URI url() { + return url; + } + + /** DNS name for this rotation */ + public String dnsName() { + return url.getHost(); + } + + /** Sanitize by translating '_' to '-' as the former is not allowed in a DNS name */ + private static String sanitize(String s) { + return s.replace('_', '-'); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java index 98ae5ed1762..b9d07249cb2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.component.Version; import com.yahoo.config.provision.ClusterSpec.Id; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import java.time.Instant; import java.util.HashMap; @@ -18,7 +19,7 @@ import java.util.Objects; */ public class Deployment { - private final Zone zone; + private final ZoneId zone; private final ApplicationRevision revision; private final Version version; private final Instant deployTime; @@ -26,11 +27,11 @@ public class Deployment { private final Map<Id, ClusterInfo> clusterInfo; private final DeploymentMetrics metrics; - public Deployment(Zone zone, ApplicationRevision revision, Version version, Instant deployTime) { + public Deployment(ZoneId zone, ApplicationRevision revision, Version version, Instant deployTime) { this(zone, revision, version, deployTime, new HashMap<>(), new HashMap<>(), new DeploymentMetrics()); } - public Deployment(Zone zone, ApplicationRevision revision, Version version, Instant deployTime, + public Deployment(ZoneId zone, ApplicationRevision revision, Version version, Instant deployTime, Map<Id, ClusterUtilization> clusterUtils, Map<Id, ClusterInfo> clusterInfo, DeploymentMetrics metrics) { Objects.requireNonNull(zone, "zone cannot be null"); Objects.requireNonNull(revision, "revision cannot be null"); @@ -49,7 +50,7 @@ public class Deployment { } /** Returns the zone this was deployed to */ - public Zone zone() { return zone; } + public ZoneId zone() { return zone; } /** Returns the revision of the application which was deployed */ public ApplicationRevision revision() { return revision; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java index 98f8c2a3d99..ec8b2d6d019 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java @@ -7,19 +7,17 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import java.time.Instant; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -161,34 +159,36 @@ public class DeploymentJobs { /** Job types that exist in the build system */ public enum JobType { - - component ("component" ), - systemTest ("system-test" , zone("test" , "us-east-1" ), zone(SystemName.cd, "test" , "cd-us-central-1")), - stagingTest ("staging-test" , zone("staging", "us-east-3" ), zone(SystemName.cd, "staging", "cd-us-central-1")), - productionCorpUsEast1 ("production-corp-us-east-1" , zone("prod" , "corp-us-east-1")), - productionUsEast3 ("production-us-east-3" , zone("prod" , "us-east-3" )), - productionUsWest1 ("production-us-west-1" , zone("prod" , "us-west-1" )), - productionUsCentral1 ("production-us-central-1" , zone("prod" , "us-central-1" )), - productionApNortheast1 ("production-ap-northeast-1" , zone("prod" , "ap-northeast-1")), - productionApNortheast2 ("production-ap-northeast-2" , zone("prod" , "ap-northeast-2")), - productionApSoutheast1 ("production-ap-southeast-1" , zone("prod" , "ap-southeast-1")), - productionEuWest1 ("production-eu-west-1" , zone("prod" , "eu-west-1" )), - productionCdUsCentral1 ("production-cd-us-central-1", zone(SystemName.cd, "prod", "cd-us-central-1")), - productionCdUsCentral2 ("production-cd-us-central-2", zone(SystemName.cd, "prod", "cd-us-central-2")); +// | enum name ------------| job name ------------------| Zone in main system ---------------------------------------| Zone in CD system ------------------------------------------- + component ("component" , null , null ), + systemTest ("system-test" , ZoneId.from("test" , "us-east-1") , ZoneId.from("test" , "cd-us-central-1")), + stagingTest ("staging-test" , ZoneId.from("staging", "us-east-3") , ZoneId.from("staging", "cd-us-central-1")), + productionCorpUsEast1 ("production-corp-us-east-1" , ZoneId.from("prod" , "corp-us-east-1") , null ), + productionUsEast3 ("production-us-east-3" , ZoneId.from("prod" , "us-east-3") , null ), + productionUsWest1 ("production-us-west-1" , ZoneId.from("prod" , "us-west-1") , null ), + productionUsCentral1 ("production-us-central-1" , ZoneId.from("prod" , "us-central-1") , null ), + productionApNortheast1 ("production-ap-northeast-1" , ZoneId.from("prod" , "ap-northeast-1") , null ), + productionApNortheast2 ("production-ap-northeast-2" , ZoneId.from("prod" , "ap-northeast-2") , null ), + productionApSoutheast1 ("production-ap-southeast-1" , ZoneId.from("prod" , "ap-southeast-1") , null ), + productionEuWest1 ("production-eu-west-1" , ZoneId.from("prod" , "eu-west-1") , null ), + productionCdUsCentral1 ("production-cd-us-central-1", null , ZoneId.from("prod" , "cd-us-central-1")), + productionCdUsCentral2 ("production-cd-us-central-2", null , ZoneId.from("prod" , "cd-us-central-2")); private final String jobName; - private final ImmutableMap<SystemName, Zone> zones; + private final ImmutableMap<SystemName, ZoneId> zones; - JobType(String jobName, Zone... zones) { + JobType(String jobName, ZoneId mainZone, ZoneId cdZone) { this.jobName = jobName; - this.zones = ImmutableMap.copyOf(Stream.of(zones).collect(Collectors.toMap(zone -> zone.system(), - zone -> zone))); + ImmutableMap.Builder<SystemName, ZoneId> builder = ImmutableMap.builder(); + if (mainZone != null) builder.put(SystemName.main, mainZone); + if (cdZone != null) builder.put(SystemName.cd, cdZone); + this.zones = builder.build(); } public String jobName() { return jobName; } /** Returns the zone for this job in the given system, or empty if this job does not have a zone */ - public Optional<Zone> zone(SystemName system) { + public Optional<ZoneId> zone(SystemName system) { return Optional.ofNullable(zones.get(system)); } @@ -207,7 +207,7 @@ public class DeploymentJobs { /** Returns the region of this job type, or null if it does not have a region */ public Optional<RegionName> region(SystemName system) { - return zone(system).map(Zone::region); + return zone(system).map(ZoneId::region); } public static JobType fromJobName(String jobName) { @@ -217,7 +217,7 @@ public class DeploymentJobs { } /** Returns the job type for the given zone */ - public static Optional<JobType> from(SystemName system, Zone zone) { + public static Optional<JobType> from(SystemName system, ZoneId zone) { return Stream.of(values()) .filter(job -> job.zone(system).filter(zone::equals).isPresent()) .findAny(); @@ -229,16 +229,9 @@ public class DeploymentJobs { case test: return Optional.of(systemTest); case staging: return Optional.of(stagingTest); } - return from(system, new Zone(environment, region)); - } - - private static Zone zone(SystemName system, String environment, String region) { - return new Zone(system, Environment.from(environment), RegionName.from(region)); + return from(system, ZoneId.from(environment, region)); } - private static Zone zone(String environment, String region) { - return new Zone(Environment.from(environment), RegionName.from(region)); - } } /** A job report. This class is immutable. */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java deleted file mode 100644 index 7e3abeb77d9..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.athenz.auth.token.PrincipalToken; -import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; - -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.concurrent.TimeUnit; - -/** - * Represents an Athenz NToken (principal token) - * - * @author bjorncs - */ -// TODO Split out encoding/decoding of token into separate class. Move NToken to controller-api. -public class NToken { - - // Max allowed skew in token timestamp (only for creation, not expiry timestamp) - private static final int ALLOWED_TIMESTAMP_OFFSET = (int) TimeUnit.SECONDS.toSeconds(300); - - private final PrincipalToken token; - - // Note: PrincipalToken does not provide any way of constructing an instance from a unsigned token string - public NToken(String signedToken) { - try { - this.token = new PrincipalToken(signedToken); - if (this.token.getSignature() == null) { - throw new IllegalArgumentException("Signature missing (unsigned token)"); - } - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Malformed NToken: " + e.getMessage()); - } - } - - public AthenzPrincipal getPrincipal() { - return new AthenzPrincipal(getDomain(), getUser()); - } - - public UserId getUser() { - return new UserId(token.getName()); - } - - public AthenzDomain getDomain() { - return new AthenzDomain(token.getDomain()); - } - - public String getToken() { - return token.getSignedToken(); - } - - public String getKeyId() { - return token.getKeyId(); - } - - public void validateSignatureAndExpiration(PublicKey publicKey) throws InvalidTokenException { - StringBuilder errorMessageBuilder = new StringBuilder(); - if (!token.validate(publicKey, ALLOWED_TIMESTAMP_OFFSET, true, errorMessageBuilder)) { - throw new InvalidTokenException("NToken is expired or has invalid signature: " + errorMessageBuilder.toString()); - } - } - - @Override - public String toString() { - return String.format("NToken(%s)", getToken()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NToken nToken = (NToken) o; - return Objects.equals(getToken(), nToken.getToken()); // PrincipalToken does not implement equals() - } - - @Override - public int hashCode() { - return Objects.hash(getToken()); // PrincipalToken does not implement hashcode() - } - - public static class Builder { - - private final String version; - private final AthenzPrincipal principal; - private final PrivateKey privateKey; - private final String keyId; - private Optional<String> salt = Optional.empty(); - private Optional<String> hostname = Optional.empty(); - private Optional<String> ip = Optional.empty(); - private OptionalLong issueTime = OptionalLong.empty(); - private OptionalLong expirationWindow = OptionalLong.empty(); - - /** - * NOTE: We must have some signature, else we might end up with problems later on as - * {@link PrincipalToken#PrincipalToken(String)} only accepts signed token - * (supplying an unsigned token to the constructor will result in inconsistent state) - */ - public Builder(String version, AthenzPrincipal principal, PrivateKey privateKey, String keyId) { - this.version = version; - this.principal = principal; - this.privateKey = privateKey; - this.keyId = keyId; - } - - public Builder salt(String salt) { - this.salt = Optional.of(salt); - return this; - } - - public Builder hostname(String hostname) { - this.hostname = Optional.of(hostname); - return this; - } - - public Builder ip(String ip) { - this.ip = Optional.of(ip); - return this; - } - - public Builder issueTime(long issueTime) { - this.issueTime = OptionalLong.of(issueTime); - return this; - } - - public Builder expirationWindow(long expirationWindow) { - this.expirationWindow = OptionalLong.of(expirationWindow); - return this; - } - - public NToken build() { - PrincipalToken token = new PrincipalToken.Builder(version, principal.getDomain().id(), principal.getName()) - .keyId(this.keyId) - .salt(this.salt.orElse(null)) - .host(this.hostname.orElse(null)) - .ip(this.ip.orElse(null)) - .issueTime(this.issueTime.orElse(0)) - .expirationWindow(this.expirationWindow.orElse(0)) - .build(); - token.sign(this.privateKey); - return new NToken(token.getSignedToken()); - } - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java deleted file mode 100644 index 59548339d11..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsException.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.athenz.zms.ZMSClientException; - -/** - * @author bjorncs - */ -public class ZmsException extends RuntimeException { - - private final int code; - - public ZmsException(ZMSClientException e) { - super(e.getMessage(), e); - this.code = e.getCode(); - } - - - public int getCode() { - return code; - } -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java deleted file mode 100644 index f400ba2eb99..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.athenz; - -import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; - -import java.util.List; - -/** - * @author bjorncs - */ -public interface ZtsClient { - - List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal); - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java index 51865be04fa..328461355db 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java @@ -6,10 +6,10 @@ import com.yahoo.jdisc.Response; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import java.util.concurrent.Executor; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java new file mode 100644 index 00000000000..939a5667a36 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzTrustStoreConfigurator.java @@ -0,0 +1,45 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.athenz.filter; + +import com.google.inject.Inject; +import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator; +import com.yahoo.jdisc.http.ssl.SslTrustStoreContext; +import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +/** + * Load trust store with Athenz CA certificates + * + * @author bjorncs + */ +public class AthenzTrustStoreConfigurator implements SslTrustStoreConfigurator { + + private final KeyStore trustStore; + + @Inject + public AthenzTrustStoreConfigurator(AthenzConfig config) { + this.trustStore = createTrustStore(new File(config.athenzCaTrustStore())); + } + + private static KeyStore createTrustStore(File trustStoreFile) { + try (FileInputStream in = new FileInputStream(trustStoreFile)) { + KeyStore trustStore = KeyStore.getInstance("JKS"); + trustStore.load(in, "changeit".toCharArray()); + return trustStore; + } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) { + throw new RuntimeException(e); + } + } + + @Override + public void configure(SslTrustStoreContext context) { + context.updateTrustStore(trustStore); + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java index f43d2d8e80e..69f59ebabe2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidator.java @@ -1,17 +1,21 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.athenz.filter; +import com.yahoo.athenz.auth.token.PrincipalToken; import com.yahoo.log.LogLevel; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; +import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore; import java.security.PublicKey; +import java.time.Duration; import java.util.Optional; import java.util.logging.Logger; -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; +import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; /** * Validates the content of an NToken: @@ -22,6 +26,9 @@ import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SE */ class NTokenValidator { + // Max allowed skew in token timestamp (only for creation, not expiry timestamp) + private static final long ALLOWED_TIMESTAMP_OFFSET = Duration.ofMinutes(5).getSeconds(); + private static final Logger log = Logger.getLogger(NTokenValidator.class.getName()); private final ZmsKeystore keystore; @@ -35,10 +42,15 @@ class NTokenValidator { } AthenzPrincipal validate(NToken token) throws InvalidTokenException { - PublicKey zmsPublicKey = getPublicKey(token.getKeyId()) + PrincipalToken principalToken = new PrincipalToken(token.getRawToken()); + PublicKey zmsPublicKey = getPublicKey(principalToken.getKeyId()) .orElseThrow(() -> new InvalidTokenException("NToken has an unknown keyId")); - validateSignatureAndExpiration(token, zmsPublicKey); - return token.getPrincipal(); + validateSignatureAndExpiration(principalToken, zmsPublicKey); + return new AthenzPrincipal( + AthenzUtils.createAthenzIdentity( + new AthenzDomain(principalToken.getDomain()), + principalToken.getName()), + token); } private Optional<PublicKey> getPublicKey(String keyId) throws InvalidTokenException { @@ -50,13 +62,13 @@ class NTokenValidator { } } - private static void validateSignatureAndExpiration(NToken token, PublicKey zmsPublicKey) throws InvalidTokenException { - try { - token.validateSignatureAndExpiration(zmsPublicKey); - } catch (InvalidTokenException e) { - // The underlying error message is not user friendly - logDebug(e.getMessage()); - throw new InvalidTokenException("NToken is expired or has invalid signature"); + private static void validateSignatureAndExpiration(PrincipalToken token, + PublicKey zmsPublicKey) throws InvalidTokenException { + StringBuilder errorMessageBuilder = new StringBuilder(); + if (!token.validate(zmsPublicKey, (int) ALLOWED_TIMESTAMP_OFFSET, true, errorMessageBuilder)) { + String message = "NToken is expired or has invalid signature: " + errorMessageBuilder.toString(); + logDebug(message); + throw new InvalidTokenException(message); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java index bfa543f160a..b4859220667 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java @@ -8,13 +8,15 @@ import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import com.yahoo.vespa.hosted.controller.restapi.application.Authorizer; import java.security.Principal; +import java.util.Optional; import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Stream; @@ -34,11 +36,13 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter { private static final Logger log = Logger.getLogger(UserAuthWithAthenzPrincipalFilter.class.getName()); private final String userAuthenticationPassThruAttribute; + private final String principalHeaderName; @Inject public UserAuthWithAthenzPrincipalFilter(ZmsKeystore zmsKeystore, Executor executor, AthenzConfig config) { super(zmsKeystore, executor, config); this.userAuthenticationPassThruAttribute = config.userAuthenticationPassThruAttribute(); + this.principalHeaderName = config.principalHeaderName(); } @Override @@ -81,13 +85,14 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter { * NOTE: The Bouncer user roles ({@link DiscFilterRequest#roles} are still intact as they are required * for {@link Authorizer#isMemberOfVespaBouncerGroup(HttpRequest)}. */ - private static void rewriteUserPrincipalToAthenz(DiscFilterRequest request) { + private void rewriteUserPrincipalToAthenz(DiscFilterRequest request) { Principal userPrincipal = request.getUserPrincipal(); log.log(LogLevel.DEBUG, () -> "Original user principal: " + userPrincipal.toString()); UserId userId = new UserId(userPrincipal.getName()); - AthenzPrincipal athenzPrincipal = AthenzUtils.createPrincipal(userId); - request.setUserPrincipal(athenzPrincipal); - request.setRemoteUser(athenzPrincipal.toYRN()); + AthenzUser athenzIdentity = AthenzUser.fromUserId(userId); + request.setRemoteUser(athenzIdentity.getFullName()); + NToken nToken = Optional.ofNullable(request.getHeader(principalHeaderName)).map(NToken::new).orElse(null); + request.setUserPrincipal(new AthenzPrincipal(athenzIdentity, nToken)); } private enum UserAuthenticationResult { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java index 1c32b35f599..a91604f937b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java @@ -10,17 +10,17 @@ import com.yahoo.athenz.auth.token.PrincipalToken; import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.athenz.zms.ZMSClient; import com.yahoo.athenz.zts.ZTSClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; import com.yahoo.vespa.hosted.controller.api.integration.security.KeyService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import java.security.PrivateKey; -import java.util.concurrent.TimeUnit; +import java.time.Duration; -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.USER_PRINCIPAL_DOMAIN; +import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.USER_PRINCIPAL_DOMAIN; /** * @author bjorncs @@ -51,7 +51,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { */ @Override public ZtsClient createZtsClientWithServicePrincipal() { - return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), config); + return new ZtsClientImpl(new ZTSClient(config.ztsUrl(), createServicePrincipal()), getServicePrivateKey(), config); } /** @@ -59,7 +59,7 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { */ @Override public ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken) { - PrincipalToken signedToken = new PrincipalToken(authorizedServiceToken.getToken()); + PrincipalToken signedToken = new PrincipalToken(authorizedServiceToken.getRawToken()); AthenzConfig.Service service = config.service(); signedToken.signForAuthorizedService( config.domain() + "." + service.name(), service.publicKeyId(), getServicePrivateKey()); @@ -75,8 +75,12 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory { // TODO bjorncs: Cache principal token SimpleServiceIdentityProvider identityProvider = new SimpleServiceIdentityProvider( - athenzPrincipalAuthority, config.domain(), service.name(), - getServicePrivateKey(), service.publicKeyId(), /*tokenTimeout*/TimeUnit.HOURS.toSeconds(1)); + athenzPrincipalAuthority, + config.domain(), + service.name(), + getServicePrivateKey(), + service.publicKeyId(), + Duration.ofMinutes(service.credentialsExpiryMinutes()).getSeconds()); return identityProvider.getIdentity(config.domain(), service.name()); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java new file mode 100644 index 00000000000..3a7a72ac8ae --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzSslContextProviderImpl.java @@ -0,0 +1,87 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.athenz.impl; + +import com.google.inject.Inject; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzSslContextProvider; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; +import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; + +/** + * @author bjorncs + */ +public class AthenzSslContextProviderImpl implements AthenzSslContextProvider { + + private final AthenzClientFactory clientFactory; + private final AthenzConfig config; + + @Inject + public AthenzSslContextProviderImpl(AthenzClientFactory clientFactory, AthenzConfig config) { + this.clientFactory = clientFactory; + this.config = config; + } + + @Override + public SSLContext get() { + return createSslContext(); + } + + private SSLContext createSslContext() { + try { + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(createKeyManagersWithServiceCertificate(clientFactory.createZtsClientWithServicePrincipal()), + createTrustManagersWithAthenzCa(config), + null); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + + private static KeyManager[] createKeyManagersWithServiceCertificate(ZtsClient ztsClient) { + try { + AthenzIdentityCertificate identityCertificate = ztsClient.getIdentityCertificate(); + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + keyStore.setKeyEntry("athenz-controller-key", + identityCertificate.getPrivateKey(), + new char[0], + new Certificate[]{identityCertificate.getCertificate()}); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, new char[0]); + return keyManagerFactory.getKeyManagers(); + } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) { + throw new RuntimeException(e); + } + } + + private static TrustManager[] createTrustManagersWithAthenzCa(AthenzConfig config) { + try { + KeyStore trustStore = KeyStore.getInstance("JKS"); + try (FileInputStream in = new FileInputStream(config.athenzCaTrustStore())) { + trustStore.load(in, "changeit".toCharArray()); + } + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + return trustManagerFactory.getTrustManagers(); + } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java index 110e06b767c..d3fac257583 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java @@ -13,12 +13,12 @@ import com.yahoo.athenz.zms.ZMSClientException; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPublicKey; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import java.util.Arrays; @@ -49,16 +49,16 @@ public class ZmsClientImpl implements ZmsClient { runOrThrow(() -> { Tenancy tenancy = new Tenancy() .setDomain(tenantDomain.id()) - .setService(service.toFullServiceName()) + .setService(service.getFullName()) .setResourceGroups(Collections.emptyList()); - zmsClient.putTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null, tenancy); + zmsClient.putTenancy(tenantDomain.id(), service.getFullName(), /*auditref*/null, tenancy); }); } @Override public void deleteTenant(AthenzDomain tenantDomain) { log("deleteTenancy(tenantDomain=%s, service=%s)", tenantDomain, service); - runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null)); + runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.getFullName(), /*auditref*/null)); } @Override @@ -66,16 +66,16 @@ public class ZmsClientImpl implements ZmsClient { List<TenantRoleAction> tenantRoleActions = createTenantRoleActions(); log("putProviderResourceGroupRoles(" + "tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)", - tenantDomain, service.getDomain().id(), service.getServiceName(), applicationName, tenantRoleActions); + tenantDomain, service.getDomain().id(), service.getName(), applicationName, tenantRoleActions); runOrThrow(() -> { ProviderResourceGroupRoles resourceGroupRoles = new ProviderResourceGroupRoles() .setDomain(service.getDomain().id()) - .setService(service.getServiceName()) + .setService(service.getName()) .setTenant(tenantDomain.id()) .setResourceGroup(applicationName.id()) .setRoles(tenantRoleActions); zmsClient.putProviderResourceGroupRoles( - tenantDomain.id(), service.getDomain().id(), service.getServiceName(), + tenantDomain.id(), service.getDomain().id(), service.getName(), applicationName.id(), /*auditref*/null, resourceGroupRoles); }); } @@ -83,34 +83,34 @@ public class ZmsClientImpl implements ZmsClient { @Override public void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName) { log("deleteProviderResourceGroupRoles(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)", - tenantDomain, service.getDomain().id(), service.getServiceName(), applicationName); + tenantDomain, service.getDomain().id(), service.getName(), applicationName); runOrThrow(() -> { zmsClient.deleteProviderResourceGroupRoles( - tenantDomain.id(), service.getDomain().id(), service.getServiceName(), applicationName.id(), /*auditref*/null); + tenantDomain.id(), service.getDomain().id(), service.getName(), applicationName.id(), /*auditref*/null); }); } @Override public boolean hasApplicationAccess( - AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { + AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { return hasAccess( - action.name(), applicationResourceString(tenantDomain, applicationName), principal); + action.name(), applicationResourceString(tenantDomain, applicationName), identity); } @Override - public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain) { - return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), principal); + public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) { + return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), identity); } /** * Used when creating tenancies. As there are no tenancy policies at this point, - * we cannot use {@link #hasTenantAdminAccess(AthenzPrincipal, AthenzDomain)} + * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)} */ @Override - public boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain) { - log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", principal); + public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) { + log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", identity); return getOrThrow( - () -> zmsClient.getMembership(domain.id(), "admin", principal.toYRN()).getIsMember()); + () -> zmsClient.getMembership(domain.id(), "admin", identity.getFullName()).getIsMember()); } @Override @@ -127,18 +127,18 @@ public class ZmsClientImpl implements ZmsClient { @Override public AthenzPublicKey getPublicKey(AthenzService service, String keyId) { - log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getServiceName(), keyId); + log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getName(), keyId); return getOrThrow(() -> { - PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getServiceName(), keyId); + PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getName(), keyId); return fromYbase64EncodedKey(entry.getKey(), keyId); }); } @Override public List<AthenzPublicKey> getPublicKeys(AthenzService service) { - log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getServiceName()); + log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getName()); return getOrThrow(() -> { - ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getServiceName()); + ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getName()); return toAthenzPublicKeys(serviceIdentity.getPublicKeys()); }); } @@ -163,10 +163,11 @@ public class ZmsClientImpl implements ZmsClient { .collect(toList()); } - private boolean hasAccess(String action, String resource, AthenzPrincipal principal) { - log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, principal); + private boolean hasAccess(String action, String resource, AthenzIdentity identity) { + log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, identity); return getOrThrow( - () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, principal.toYRN()).getGranted()); + () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, identity.getFullName()) + .getGranted()); } private static void log(String format, Object... args) { @@ -178,7 +179,7 @@ public class ZmsClientImpl implements ZmsClient { wrappedCode.run(); } catch (ZMSClientException e) { logWarning(e); - throw new ZmsException(e); + throw new ZmsException(e.getCode(), e); } } @@ -187,7 +188,7 @@ public class ZmsClientImpl implements ZmsClient { return wrappedCode.get(); } catch (ZMSClientException e) { logWarning(e); - throw new ZmsException(e); + throw new ZmsException(e.getCode(), e); } } @@ -197,7 +198,7 @@ public class ZmsClientImpl implements ZmsClient { private String resourceStringPrefix(AthenzDomain tenantDomain) { return String.format("%s:service.%s.tenant.%s", - service.getDomain().id(), service.getServiceName(), tenantDomain.id()); + service.getDomain().id(), service.getName(), tenantDomain.id()); } private String tenantResourceString(AthenzDomain tenantDomain) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java index fd58a3daba7..513434f7273 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsKeystoreImpl.java @@ -3,11 +3,11 @@ package com.yahoo.vespa.hosted.controller.athenz.impl; import com.google.inject.Inject; import com.yahoo.log.LogLevel; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPublicKey; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore; import java.security.PublicKey; import java.util.List; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java index 1111e56c742..a29f2e81fba 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java @@ -1,18 +1,27 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.athenz.impl; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.zts.InstanceRefreshRequest; +import com.yahoo.athenz.zts.RoleCertificateRequest; import com.yahoo.athenz.zts.TenantDomains; import com.yahoo.athenz.zts.ZTSClient; import com.yahoo.athenz.zts.ZTSClientException; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZtsException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzRoleCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsException; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.List; +import java.util.function.Supplier; import java.util.logging.Logger; import static java.util.stream.Collectors.toList; @@ -26,25 +35,79 @@ public class ZtsClientImpl implements ZtsClient { private final ZTSClient ztsClient; private final AthenzService service; + private final PrivateKey privateKey; + private final String certificateDnsDomain; + private final Duration certExpiry; - public ZtsClientImpl(ZTSClient ztsClient, AthenzConfig config) { + public ZtsClientImpl(ZTSClient ztsClient, PrivateKey privateKey, AthenzConfig config) { this.ztsClient = ztsClient; this.service = new AthenzService(config.domain(), config.service().name()); + this.privateKey = privateKey; + this.certificateDnsDomain = config.certDnsDomain(); + this.certExpiry = Duration.ofMinutes(config.service().credentialsExpiryMinutes()); } @Override - public List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal) { - log.log(LogLevel.DEBUG, String.format( - "getTenantDomains(domain=%s, username=%s, rolename=admin, service=%s)", - service.getDomain().id(), principal, service.getServiceName())); - try { + public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, String.format( + "getTenantDomains(domain=%s, identity=%s, rolename=admin, service=%s)", + service.getDomain().id(), identity.getFullName(), service.getFullName())); TenantDomains domains = ztsClient.getTenantDomains( - service.getDomain().id(), principal.toYRN(), "admin", service.getServiceName()); + service.getDomain().id(), identity.getFullName(), "admin", service.getName()); return domains.getTenantDomainNames().stream() .map(AthenzDomain::new) .collect(toList()); + }); + } + + @Override + public AthenzIdentityCertificate getIdentityCertificate() { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, + String.format("postInstanceRefreshRequest(service=%s)", service.getFullName())); + InstanceRefreshRequest req = + ZTSClient.generateInstanceRefreshRequest( + service.getDomain().id(), + service.getName(), + privateKey, + certificateDnsDomain, + (int) certExpiry.getSeconds()); + X509Certificate certificate = Crypto.loadX509Certificate( + ztsClient.postInstanceRefreshRequest(service.getDomain().id(), service.getName(), req) + .getCertificate()); + return new AthenzIdentityCertificate(certificate, privateKey); + }); + } + + @Override + public AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName) { + return getOrThrow(() -> { + log.log(LogLevel.DEBUG, + String.format("postRoleCertificateRequest(service=%s, roleDomain=%s, roleName=%s)", + service.getFullName(), roleDomain.id(), roleName)); + RoleCertificateRequest req = + ZTSClient.generateRoleCertificateRequest( + service.getDomain().id(), + service.getName(), + roleDomain.id(), + roleName, + privateKey, + certificateDnsDomain, + (int)certExpiry.getSeconds()); + X509Certificate roleCertificate = Crypto.loadX509Certificate( + ztsClient.postRoleCertificateRequest(roleDomain.id(), roleName, req) + .getToken()); + return new AthenzRoleCertificate(roleCertificate, privateKey); + }); + } + + private static <T> T getOrThrow(Supplier<T> wrappedCode) { + try { + return wrappedCode.get(); } catch (ZTSClientException e) { - throw new ZtsException(e); + log.warning("Error from Athenz: " + e.getMessage()); + throw new ZtsException(e.getCode(), e); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java index d4a2d77c115..52a1f2d477d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java @@ -3,10 +3,10 @@ package com.yahoo.vespa.hosted.controller.athenz.mock; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java index 017e8c7be44..c633d780e30 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java @@ -3,8 +3,8 @@ package com.yahoo.vespa.hosted.controller.athenz.mock; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; import java.util.HashMap; import java.util.HashSet; @@ -26,8 +26,8 @@ public class AthenzDbMock { public static class Domain { public final AthenzDomain name; - public final Set<AthenzPrincipal> admins = new HashSet<>(); - public final Set<AthenzPrincipal> tenantAdmins = new HashSet<>(); + public final Set<AthenzIdentity> admins = new HashSet<>(); + public final Set<AthenzIdentity> tenantAdmins = new HashSet<>(); public final Map<ApplicationId, Application> applications = new HashMap<>(); public boolean isVespaTenant = false; @@ -35,13 +35,13 @@ public class AthenzDbMock { this.name = name; } - public Domain admin(AthenzPrincipal user) { - admins.add(user); + public Domain admin(AthenzIdentity identity) { + admins.add(identity); return this; } - public Domain tenantAdmin(AthenzPrincipal user) { - tenantAdmins.add(user); + public Domain tenantAdmin(AthenzIdentity identity) { + tenantAdmins.add(identity); return this; } @@ -56,7 +56,7 @@ public class AthenzDbMock { public static class Application { - public final Map<ApplicationAction, Set<AthenzPrincipal>> acl = new HashMap<>(); + public final Map<ApplicationAction, Set<AthenzIdentity>> acl = new HashMap<>(); public Application() { acl.put(ApplicationAction.deploy, new HashSet<>()); @@ -64,8 +64,8 @@ public class AthenzDbMock { acl.put(ApplicationAction.write, new HashSet<>()); } - public Application addRoleMember(ApplicationAction action, AthenzPrincipal user) { - acl.get(action).add(user); + public Application addRoleMember(ApplicationAction action, AthenzIdentity identity) { + acl.get(action).add(identity); return this; } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java index b2e657eae09..4b50a34094a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java @@ -1,15 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.athenz.mock; -import com.yahoo.athenz.zms.ZMSClientException; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; -import com.yahoo.vespa.hosted.controller.athenz.AthenzService; -import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPublicKey; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import java.util.ArrayList; import java.util.List; @@ -61,28 +60,28 @@ public class ZmsClientMock implements ZmsClient { } @Override - public boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { + public boolean hasApplicationAccess(AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { log("hasApplicationAccess(principal='%s', action='%s', tenantDomain='%s', applicationName='%s')", - principal, action, tenantDomain, applicationName); + identity, action, tenantDomain, applicationName); AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, true); AthenzDbMock.Application application = domain.applications.get(applicationName); if (application == null) { throw zmsException(400, "Application '%s' not found", applicationName); } - return domain.admins.contains(principal) || application.acl.get(action).contains(principal); + return domain.admins.contains(identity) || application.acl.get(action).contains(identity); } @Override - public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain) { - log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", principal, tenantDomain); - return isDomainAdmin(principal, tenantDomain) || - getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(principal); + public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) { + log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", identity, tenantDomain); + return isDomainAdmin(identity, tenantDomain) || + getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(identity); } @Override - public boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain) { - log("isDomainAdmin(principal='%s', domain='%s')", principal, domain); - return getDomainOrThrow(domain, false).admins.contains(principal); + public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) { + log("isDomainAdmin(principal='%s', domain='%s')", identity, domain); + return getDomainOrThrow(domain, false).admins.contains(identity); } @Override @@ -111,7 +110,7 @@ public class ZmsClientMock implements ZmsClient { } private static ZmsException zmsException(int code, String message, Object... args) { - return new ZmsException(new ZMSClientException(code, String.format(message, args))); + return new ZmsException(code, String.format(message, args)); } private static void log(String format, Object... args) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java index f21bc011273..d778fb550ed 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java @@ -1,10 +1,21 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.athenz.mock; +import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzRoleCertificate; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZtsClient; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -24,11 +35,51 @@ public class ZtsClientMock implements ZtsClient { } @Override - public List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal) { - log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", principal); + public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) { + log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", identity); return athenz.domains.values().stream() - .filter(domain -> domain.tenantAdmins.contains(principal) || domain.admins.contains(principal)) + .filter(domain -> domain.tenantAdmins.contains(identity) || domain.admins.contains(identity)) .map(domain -> domain.name) .collect(toList()); } + + @Override + public AthenzIdentityCertificate getIdentityCertificate() { + log.log(Level.INFO, "getIdentityCertificate()"); + try { + KeyPair keyPair = createKeyPair(); + String subject = "CN=controller"; + return new AthenzIdentityCertificate(createCertificate(keyPair, subject), keyPair.getPrivate()); + } catch (NoSuchAlgorithmException | OperatorCreationException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public AthenzRoleCertificate getRoleCertificate(AthenzDomain roleDomain, String roleName) { + log.log(Level.INFO, + String.format("getRoleCertificate(roleDomain=%s, roleName=%s)", roleDomain.id(), roleDomain)); + try { + KeyPair keyPair = createKeyPair(); + String subject = String.format("CN=%s:role.%s", roleDomain.id(), roleName); + return new AthenzRoleCertificate(createCertificate(keyPair, subject), keyPair.getPrivate()); + } catch (NoSuchAlgorithmException | OperatorCreationException | IOException e) { + throw new RuntimeException(e); + } + } + + private static X509Certificate createCertificate(KeyPair keyPair, String subject) throws + OperatorCreationException, IOException { + PKCS10CertificationRequest csr = + Crypto.getPKCS10CertRequest( + Crypto.generateX509CSR(keyPair.getPrivate(), subject, null)); + return Crypto.generateX509Certificate(csr, keyPair.getPrivate(), new X500Name(subject), 3600, false); + } + + private static KeyPair createKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(512); + return keyGen.genKeyPair(); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java index 7c06ef27ce9..2bf64571bdf 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.LockedApplication; @@ -46,7 +46,6 @@ public class DeploymentOrder { /** Returns a list of jobs to trigger after the given job */ // TODO: This does too much - should just tell us the order, as advertised - // TODO: You're next! public List<JobType> nextAfter(JobType job, LockedApplication application) { if ( ! application.deploying().isPresent()) { // Change was cancelled return Collections.emptyList(); @@ -106,9 +105,9 @@ public class DeploymentOrder { /** Returns deployments sorted according to declared zones */ public List<Deployment> sortBy(List<DeploymentSpec.DeclaredZone> zones, Collection<Deployment> deployments) { - List<Zone> productionZones = zones.stream() + List<ZoneId> productionZones = zones.stream() .filter(z -> z.region().isPresent()) - .map(z -> new Zone(z.environment(), z.region().get())) + .map(z -> ZoneId.from(z.environment(), z.region().get())) .collect(toList()); return deployments.stream() .sorted(comparingInt(deployment -> productionZones.indexOf(deployment.zone()))) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java index 1eee727214b..f0c950b024b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java @@ -4,8 +4,7 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.curator.Lock; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ApplicationController; import com.yahoo.vespa.hosted.controller.Controller; @@ -28,7 +27,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; import java.util.logging.Logger; /** @@ -79,7 +77,7 @@ public class DeploymentTrigger { * @param report information about the job that just completed */ public void triggerFromCompletion(JobReport report) { - applications().lockedOrThrow(report.applicationId(), application -> { + applications().lockOrThrow(report.applicationId(), application -> { application = application.withJobCompletion(report, clock.instant(), controller); // Handle successful starting and ending @@ -132,7 +130,7 @@ public class DeploymentTrigger { ApplicationList applications = ApplicationList.from(applications().asList()); applications = applications.notPullRequest(); for (Application application : applications.asList()) - applications().lockedIfPresent(application.id(), this::triggerReadyJobs); + applications().lockIfPresent(application.id(), this::triggerReadyJobs); } /** Find the next step to trigger if any, and triggers it */ @@ -219,7 +217,7 @@ public class DeploymentTrigger { * @throws IllegalArgumentException if this application already have an ongoing change */ public void triggerChange(ApplicationId applicationId, Change change) { - applications().lockedOrThrow(applicationId, application -> { + applications().lockOrThrow(applicationId, application -> { if (application.deploying().isPresent() && ! application.deploymentJobs().hasFailures()) throw new IllegalArgumentException("Could not start " + change + " on " + application + ": " + application.deploying().get() + " is already in progress"); @@ -238,7 +236,7 @@ public class DeploymentTrigger { * @param applicationId the application to trigger */ public void cancelChange(ApplicationId applicationId) { - applications().lockedOrThrow(applicationId, application -> { + applications().lockOrThrow(applicationId, application -> { buildSystem.removeJobs(application.id()); applications().store(application.withDeploying(Optional.empty())); }); @@ -360,7 +358,7 @@ public class DeploymentTrigger { */ private boolean isOnNewerVersionInProductionThan(Version version, Application application, JobType job) { if ( ! job.isProduction()) return false; - Optional<Zone> zone = job.zone(controller.system()); + Optional<ZoneId> zone = job.zone(controller.system()); if ( ! zone.isPresent()) return false; Deployment existingDeployment = application.deployments().get(zone.get()); if (existingDeployment == null) return false; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java index 09f8df58205..2b7260d5ffa 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java @@ -1,7 +1,6 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.Tenant; @@ -86,7 +85,7 @@ public class ApplicationOwnershipConfirmer extends Maintainer { } protected void store(IssueId issueId, ApplicationId applicationId) { - controller().applications().lockedIfPresent(applicationId, application -> + controller().applications().lockIfPresent(applicationId, application -> controller().applications().store(application.withOwnershipIssueId(issueId))); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java index ae617f87be6..ad7fa90967b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java @@ -4,10 +4,9 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.curator.Lock; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.LockedApplication; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeList; import com.yahoo.vespa.hosted.controller.application.ApplicationList; @@ -46,7 +45,7 @@ public class ClusterInfoMaintainer extends Maintainer { return node.membership.clusterId; } - private Map<ClusterSpec.Id, ClusterInfo> getClusterInfo(NodeList nodes, Zone zone) { + private Map<ClusterSpec.Id, ClusterInfo> getClusterInfo(NodeList nodes, ZoneId zone) { Map<ClusterSpec.Id, ClusterInfo> infoMap = new HashMap<>(); // Group nodes by clusterid @@ -65,7 +64,8 @@ public class ClusterInfoMaintainer extends Maintainer { double cpu = 0; double mem = 0; double disk = 0; - if (zone.nodeFlavors().isPresent()) { + // TODO: This code was never run. Reenable when flavours are available from a FlavorRegistry or something, or remove. + /*if (zone.nodeFlavors().isPresent()) { Optional<Flavor> flavorOptional = zone.nodeFlavors().get().getFlavor(node.flavor); if ((flavorOptional.isPresent())) { Flavor flavor = flavorOptional.get(); @@ -73,7 +73,7 @@ public class ClusterInfoMaintainer extends Maintainer { mem = flavor.getMinMainMemoryAvailableGb(); disk = flavor.getMinMainMemoryAvailableGb(); } - } + }*/ // Add to map List<String> hostnames = clusterNodes.stream().map(node1 -> node1.hostname).collect(Collectors.toList()); @@ -93,7 +93,7 @@ public class ClusterInfoMaintainer extends Maintainer { try { NodeList nodes = controller().applications().configserverClient().getNodeList(deploymentId); Map<ClusterSpec.Id, ClusterInfo> clusterInfo = getClusterInfo(nodes, deployment.zone()); - controller().applications().lockedIfPresent(application.id(), lockedApplication -> + controller().applications().lockIfPresent(application.id(), lockedApplication -> controller.applications().store(lockedApplication.withClusterInfo(deployment.zone(), clusterInfo))); } catch (IOException | IllegalArgumentException e) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java index 3744be67135..58e32344372 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java @@ -3,8 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.curator.Lock; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; @@ -30,7 +29,7 @@ public class ClusterUtilizationMaintainer extends Maintainer { this.controller = controller; } - private Map<ClusterSpec.Id, ClusterUtilization> getUpdatedClusterUtilizations(ApplicationId app, Zone zone) { + private Map<ClusterSpec.Id, ClusterUtilization> getUpdatedClusterUtilizations(ApplicationId app, ZoneId zone) { Map<String, MetricsService.SystemMetrics> systemMetrics = controller.metricsService().getSystemMetrics(app, zone); Map<ClusterSpec.Id, ClusterUtilization> utilizationMap = new HashMap<>(); @@ -50,7 +49,7 @@ public class ClusterUtilizationMaintainer extends Maintainer { Map<ClusterSpec.Id, ClusterUtilization> clusterUtilization = getUpdatedClusterUtilizations(application.id(), deployment.zone()); - controller().applications().lockedIfPresent(application.id(), lockedApplication -> + controller().applications().lockIfPresent(application.id(), lockedApplication -> controller().applications().store(lockedApplication.withClusterUtilization(deployment.zone(), clusterUtilization))); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java index bc2112ac0ca..4e9dd94d8e5 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.component.AbstractComponent; import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues; import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef; @@ -34,11 +35,13 @@ public class ControllerMaintenance extends AbstractComponent { private final ClusterUtilizationMaintainer clusterUtilizationMaintainer; private final DeploymentMetricsMaintainer deploymentMetricsMaintainer; private final ApplicationOwnershipConfirmer applicationOwnershipConfirmer; + private final DnsMaintainer dnsMaintainer; @SuppressWarnings("unused") // instantiated by Dependency Injection public ControllerMaintenance(MaintainerConfig maintainerConfig, Controller controller, CuratorDb curator, JobControl jobControl, Metric metric, Chef chefClient, - DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues) { + DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues, + NameService nameService) { Duration maintenanceInterval = Duration.ofMinutes(maintainerConfig.intervalMinutes()); this.jobControl = jobControl; deploymentExpirer = new DeploymentExpirer(controller, maintenanceInterval, jobControl); @@ -52,6 +55,7 @@ public class ControllerMaintenance extends AbstractComponent { clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl); deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(10), jobControl); applicationOwnershipConfirmer = new ApplicationOwnershipConfirmer(controller, Duration.ofHours(12), jobControl, ownershipIssues); + dnsMaintainer = new DnsMaintainer(controller, Duration.ofHours(1), jobControl, nameService); } public Upgrader upgrader() { return upgrader; } @@ -72,6 +76,7 @@ public class ControllerMaintenance extends AbstractComponent { clusterInfoMaintainer.deconstruct(); deploymentMetricsMaintainer.deconstruct(); applicationOwnershipConfirmer.deconstruct(); + dnsMaintainer.deconstruct(); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java index eb44229e790..5638ff28904 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java @@ -53,14 +53,9 @@ public class DeploymentExpirer extends Maintainer { } public static boolean hasExpired(ZoneRegistry zoneRegistry, Deployment deployment, Instant now) { - return zoneRegistry.getDeploymentTimeToLive(deployment.zone().environment(), deployment.zone().region()) - .map(duration -> getExpiration(deployment, duration)) - .map(now::isAfter) + return zoneRegistry.getDeploymentTimeToLive(deployment.zone()) + .map(timeToLive -> deployment.at().plus(timeToLive).isBefore(now)) .orElse(false); } - private static Instant getExpiration(Deployment instance, Duration ttl) { - return instance.at().plus(ttl); - } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java index ae6ba364d25..324868878af 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.Tenant; @@ -131,7 +130,7 @@ public class DeploymentIssueReporter extends Maintainer { } private void store(ApplicationId id, IssueId issueId) { - controller().applications().lockedIfPresent(id, application -> + controller().applications().lockIfPresent(id, application -> controller().applications().store(application.withDeploymentIssueId(issueId))); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java index 13eb5075f34..821efba013d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.maintenance;// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; @@ -34,7 +33,7 @@ public class DeploymentMetricsMaintainer extends Maintainer { boolean hasWarned = false; for (Application application : ApplicationList.from(controller().applications().asList()).notPullRequest().asList()) { try { - controller().applications().lockedIfPresent(application.id(), lockedApplication -> + controller().applications().lockIfPresent(application.id(), lockedApplication -> controller().applications().store(lockedApplication.with(controller().metricsService().getApplicationMetrics(application.id())))); for (Deployment deployment : application.deployments().values()) { @@ -46,7 +45,7 @@ public class DeploymentMetricsMaintainer extends Maintainer { deploymentMetrics.queryLatencyMillis(), deploymentMetrics.writeLatencyMillis()); - controller().applications().lockedIfPresent(application.id(), lockedApplication -> + controller().applications().lockIfPresent(application.id(), lockedApplication -> controller().applications().store(lockedApplication.with(deployment.zone(), appMetrics))); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java new file mode 100644 index 00000000000..89394bf4dd9 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java @@ -0,0 +1,67 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.maintenance; + +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; +import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; +import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData; +import com.yahoo.vespa.hosted.controller.application.ApplicationRotation; +import com.yahoo.vespa.hosted.controller.rotation.Rotation; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; +import com.yahoo.vespa.hosted.controller.rotation.RotationLock; +import com.yahoo.vespa.hosted.controller.rotation.RotationRepository; + +import java.time.Duration; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * Performs DNS maintenance tasks such as removing DNS aliases for unassigned rotations. + * + * @author mpolden + */ +public class DnsMaintainer extends Maintainer { + + private static final Logger log = Logger.getLogger(DnsMaintainer.class.getName()); + + private final NameService nameService; + + public DnsMaintainer(Controller controller, Duration interval, JobControl jobControl, + NameService nameService) { + super(controller, interval, jobControl); + this.nameService = nameService; + } + + private RotationRepository rotationRepository() { + return controller().applications().rotationRepository(); + } + + @Override + protected void maintain() { + try (RotationLock lock = rotationRepository().lock()) { + Map<RotationId, Rotation> unassignedRotations = rotationRepository().availableRotations(lock); + unassignedRotations.values().forEach(this::removeDnsAlias); + } + } + + /** Remove DNS alias for unassigned rotation */ + private void removeDnsAlias(Rotation rotation) { + // When looking up CNAME by data, the data must be a FQDN + Optional<Record> record = nameService.findRecord(Record.Type.CNAME, RecordData.fqdn(rotation.name())); + record.filter(this::canUpdate) + .ifPresent(r -> { + log.warning(String.format("Want to remove DNS record %s (%s) because it points to the unassigned " + + "rotation %s (%s)", record.get().id().asString(), + record.get().name().asString(), rotation.id().asString(), rotation.name())); + // TODO: Actually remove the record + //nameService.removeRecord(r.id()); + }); + } + + /** Returns whether we can update the given record */ + private boolean canUpdate(Record record) { + return record.name().asString().endsWith(ApplicationRotation.DNS_SUFFIX); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java index 01e53ce4f79..ab388ca9a9f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java @@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef; import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNode; import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult; import com.yahoo.vespa.hosted.controller.application.ApplicationList; +import com.yahoo.vespa.hosted.controller.rotation.RotationLock; import java.time.Clock; import java.time.Duration; @@ -23,11 +24,14 @@ import java.util.Optional; /** * @author mortent + * @author mpolden */ public class MetricsReporter extends Maintainer { public static final String convergeMetric = "seconds.since.last.chef.convergence"; public static final String deploymentFailMetric = "deployment.failurePercentage"; + public static final String remainingRotations = "remaining_rotations"; + private final Metric metric; private final Chef chefClient; private final Clock clock; @@ -51,6 +55,14 @@ public class MetricsReporter extends Maintainer { public void maintain() { reportChefMetrics(); reportDeploymentMetrics(); + reportRemainingRotations(); + } + + private void reportRemainingRotations() { + try (RotationLock lock = controller().applications().rotationRepository().lock()) { + int availableRotations = controller().applications().rotationRepository().availableRotations(lock).size(); + metric.set(remainingRotations, availableRotations, metric.createContext(Collections.emptyMap())); + } } private void reportChefMetrics() { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java index 23316a74aae..762f12c3e8a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java @@ -8,7 +8,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; @@ -27,6 +27,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; import java.time.Instant; import java.util.ArrayList; @@ -55,6 +56,7 @@ public class ApplicationSerializer { private final String ownershipIssueIdField = "ownershipIssueId"; private final String writeQualityField = "writeQuality"; private final String queryQualityField = "queryQuality"; + private final String rotationField = "rotation"; // Deployment fields private final String zoneField = "zone"; @@ -114,7 +116,7 @@ public class ApplicationSerializer { private final String deploymentMetricsQueryLatencyField = "queryLatencyMillis"; private final String deploymentMetricsWriteLatencyField = "writeLatencyMillis"; - + // ------------------ Serialization public Slime toSlime(Application application) { @@ -130,6 +132,7 @@ public class ApplicationSerializer { application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value())); root.setDouble(queryQualityField, application.metrics().queryServiceQuality()); root.setDouble(writeQualityField, application.metrics().writeServiceQuality()); + application.rotation().ifPresent(rotation -> root.setString(rotationField, rotation.id().asString())); return slime; } @@ -139,7 +142,7 @@ public class ApplicationSerializer { } private void deploymentToSlime(Deployment deployment, Cursor object) { - zoneToSlime(deployment.zone(), object.setObject(zoneField)); + zoneIdToSlime(deployment.zone(), object.setObject(zoneField)); object.setString(versionField, deployment.version().toString()); object.setLong(deployTimeField, deployment.at().toEpochMilli()); toSlime(deployment.revision(), object.setObject(applicationPackageRevisionField)); @@ -191,7 +194,7 @@ public class ApplicationSerializer { object.setDouble(clusterUtilsDiskBusyField, utils.getDiskBusy()); } - private void zoneToSlime(Zone zone, Cursor object) { + private void zoneIdToSlime(ZoneId zone, Cursor object) { object.setString(environmentField, zone.environment().value()); object.setString(regionField, zone.region().value()); } @@ -267,9 +270,10 @@ public class ApplicationSerializer { Optional<IssueId> ownershipIssueId = optionalString(root.field(ownershipIssueIdField)).map(IssueId::from); ApplicationMetrics metrics = new ApplicationMetrics(root.field(queryQualityField).asDouble(), root.field(writeQualityField).asDouble()); + Optional<RotationId> rotation = rotationFromSlime(root.field(rotationField)); - return new Application(id, deploymentSpec, validationOverrides, deployments, - deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics); + return new Application(id, deploymentSpec, validationOverrides, deployments, deploymentJobs, deploying, + outstandingChange, ownershipIssueId, metrics, rotation); } private List<Deployment> deploymentsFromSlime(Inspector array) { @@ -279,13 +283,13 @@ public class ApplicationSerializer { } private Deployment deploymentFromSlime(Inspector deploymentObject) { - return new Deployment(zoneFromSlime(deploymentObject.field(zoneField)), + return new Deployment(zoneIdFromSlime(deploymentObject.field(zoneField)), applicationRevisionFromSlime(deploymentObject.field(applicationPackageRevisionField)).get(), Version.fromString(deploymentObject.field(versionField).asString()), Instant.ofEpochMilli(deploymentObject.field(deployTimeField).asLong()), - clusterUtilsMapFromSlime(deploymentObject.field(clusterUtilsField)), - clusterInfoMapFromSlime(deploymentObject.field(clusterInfoField)), - deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField))); + clusterUtilsMapFromSlime(deploymentObject.field(clusterUtilsField)), + clusterInfoMapFromSlime(deploymentObject.field(clusterInfoField)), + deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField))); } private DeploymentMetrics deploymentMetricsFromSlime(Inspector object) { @@ -334,9 +338,8 @@ public class ApplicationSerializer { return new ClusterInfo(flavor, cost, flavorCpu, flavorMem, flavorDisk, ClusterSpec.Type.from(type), hostnames); } - private Zone zoneFromSlime(Inspector object) { - return new Zone(Environment.from(object.field(environmentField).asString()), - RegionName.from(object.field(regionField).asString())); + private ZoneId zoneIdFromSlime(Inspector object) { + return ZoneId.from(object.field(environmentField).asString(), object.field(regionField).asString()); } private Optional<ApplicationRevision> applicationRevisionFromSlime(Inspector object) { @@ -403,6 +406,10 @@ public class ApplicationSerializer { Instant.ofEpochMilli(object.field(atField).asLong()))); } + private Optional<RotationId> rotationFromSlime(Inspector field) { + return field.valid() ? optionalString(field).map(RotationId::new) : Optional.empty(); + } + private Optional<Long> optionalLong(Inspector field) { return field.valid() ? Optional.of(field.asLong()) : Optional.empty(); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java index 3fbfdd31808..fb6608ea643 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerDb.java @@ -6,12 +6,10 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.identifiers.Identifier; -import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import java.util.List; import java.util.Optional; -import java.util.Set; /** * Used to store the permanent data of the controller. @@ -19,55 +17,46 @@ import java.util.Set; * @author Stian Kristoffersen * @author bratseth */ -public abstract class ControllerDb { +public interface ControllerDb { // --------- Tenants - public abstract void createTenant(Tenant tenant); + void createTenant(Tenant tenant); - public abstract void updateTenant(Tenant tenant) throws PersistenceException; + // TODO: Remove exception from all signatures + void updateTenant(Tenant tenant) throws PersistenceException; - public abstract void deleteTenant(TenantId tenantId) throws PersistenceException; + void deleteTenant(TenantId tenantId) throws PersistenceException; - public abstract Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException; + Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException; - public abstract List<Tenant> listTenants(); + List<Tenant> listTenants(); // --------- Applications // ONLY call this from ApplicationController.store() - public abstract void store(Application application); + void store(Application application); - public abstract void deleteApplication(ApplicationId applicationId); + void deleteApplication(ApplicationId applicationId); - public abstract Optional<Application> getApplication(ApplicationId applicationId); + Optional<Application> getApplication(ApplicationId applicationId); /** Returns all applications */ - public abstract List<Application> listApplications(); + List<Application> listApplications(); /** Returns all applications of a tenant */ - public abstract List<Application> listApplications(TenantId tenantId); - - // --------- Rotations - - public abstract Set<RotationId> getRotations(); - - public abstract Set<RotationId> getRotations(ApplicationId applicationId); - - public abstract boolean assignRotation(RotationId rotationId, ApplicationId applicationId); - - public abstract Set<RotationId> deleteRotations(ApplicationId applicationId); + List<Application> listApplications(TenantId tenantId); /** Returns the given elements joined by dot "." */ - protected String path(Identifier... elements) { + default String path(Identifier... elements) { return Joiner.on(".").join(elements); } - protected String path(String... elements) { + default String path(String... elements) { return Joiner.on(".").join(elements); } - protected String path(ApplicationId applicationId) { + default String path(ApplicationId applicationId) { return applicationId.tenant().value() + "." + applicationId.application().value() + "." + applicationId.instance().value(); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java index e5616f025ce..a3bb191fc38 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java @@ -39,6 +39,8 @@ public class CuratorDb { private static final Path root = Path.fromString("/controller/v1"); + private static final Path lockRoot = root.append("locks"); + private static final Duration defaultLockTimeout = Duration.ofMinutes(5); private final StringSetSerializer stringSetSerializer = new StringSetSerializer(); @@ -67,6 +69,10 @@ public class CuratorDb { return lock(lockPath(id), timeout); } + public Lock lockRotations() { + return lock(lockRoot.append("rotations"), defaultLockTimeout); + } + /** Create a reentrant lock */ private Lock lock(Path path, Duration timeout) { Lock lock = locks.computeIfAbsent(path, (pathArg) -> new Lock(pathArg.getAbsolute(), curator)); @@ -75,18 +81,18 @@ public class CuratorDb { } public Lock lockInactiveJobs() { - return lock(root.append("locks").append("inactiveJobsLock"), defaultLockTimeout); + return lock(lockRoot.append("inactiveJobsLock"), defaultLockTimeout); } public Lock lockJobQueues() { - return lock(root.append("locks").append("jobQueuesLock"), defaultLockTimeout); + return lock(lockRoot.append("jobQueuesLock"), defaultLockTimeout); } public Lock lockMaintenanceJob(String jobName) { // Use a short timeout such that if maintenance jobs are started at about the same time on different nodes // and the maintenance job takes a long time to complete, only one of the nodes will run the job // in each maintenance interval - return lock(root.append("locks").append("maintenanceJobLocks").append(jobName), Duration.ofSeconds(1)); + return lock(lockRoot.append("maintenanceJobLocks").append(jobName), Duration.ofSeconds(1)); } public Lock lockProvisionState(String provisionStateId) { @@ -94,11 +100,11 @@ public class CuratorDb { } public Lock lockVespaServerPool() { - return lock(root.append("locks").append("vespaServerPoolLock"), Duration.ofSeconds(1)); + return lock(lockRoot.append("vespaServerPoolLock"), Duration.ofSeconds(1)); } public Lock lockOpenStackServerPool() { - return lock(root.append("locks").append("openStackServerPoolLock"), Duration.ofSeconds(1)); + return lock(lockRoot.append("openStackServerPoolLock"), Duration.ofSeconds(1)); } // -------------- Read and write -------------------------------------------------- @@ -222,19 +228,15 @@ public class CuratorDb { // -------------- Paths -------------------------------------------------- - private Path systemVersionPath() { - return root.append("systemVersion"); - } - private Path lockPath(TenantId tenant) { - Path lockPath = root.append("locks") + Path lockPath = lockRoot .append(tenant.id()); curator.create(lockPath); return lockPath; } private Path lockPath(ApplicationId application) { - Path lockPath = root.append("locks") + Path lockPath = lockRoot .append(application.tenant().value()) .append(application.application().value()) .append(application.instance().value()); @@ -243,7 +245,7 @@ public class CuratorDb { } private Path lockPath(String provisionId) { - Path lockPath = root.append("locks") + Path lockPath = lockRoot .append(provisionStatePath()) .append(provisionId); curator.create(lockPath); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java index ab240b9dea9..2c5d77c7773 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MemoryControllerDb.java @@ -6,7 +6,6 @@ import com.yahoo.vespa.hosted.controller.AlreadyExistsException; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.NotExistsException; import com.yahoo.vespa.hosted.controller.api.Tenant; -import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import java.util.ArrayList; @@ -14,7 +13,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; /** @@ -22,11 +20,10 @@ import java.util.stream.Collectors; * * @author Stian Kristoffersen */ -public class MemoryControllerDb extends ControllerDb { +public class MemoryControllerDb implements ControllerDb { private final Map<TenantId, Tenant> tenants = new HashMap<>(); private final Map<String, Application> applications = new HashMap<>(); - private final Map<RotationId, ApplicationId> rotationAssignments = new HashMap<>(); @Override public void createTenant(Tenant tenant) { @@ -52,7 +49,7 @@ public class MemoryControllerDb extends ControllerDb { } @Override - public Optional<Tenant> getTenant(TenantId tenantId) throws PersistenceException { + public Optional<Tenant> getTenant(TenantId tenantId) { return Optional.ofNullable(tenants.get(tenantId)); } @@ -88,36 +85,4 @@ public class MemoryControllerDb extends ControllerDb { .collect(Collectors.toList()); } - @Override - public Set<RotationId> getRotations() { - return rotationAssignments.keySet(); - } - - @Override - public Set<RotationId> getRotations(ApplicationId applicationId) { - return rotationAssignments.entrySet().stream() - .filter(entry -> entry.getValue().equals(applicationId)) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - } - - @Override - public boolean assignRotation(RotationId rotationId, ApplicationId applicationId) { - if (rotationAssignments.containsKey(rotationId)) { - return false; - } else { - rotationAssignments.put(rotationId, applicationId); - return true; - } - } - - @Override - public Set<RotationId> deleteRotations(ApplicationId applicationId) { - Set<RotationId> rotations = getRotations(applicationId); - for (RotationId rotation : rotations) { - rotationAssignments.remove(rotation); - } - return rotations; - } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java index e8b68d0c55a..e67b96c22ad 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.io.IOUtils; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; @@ -54,11 +54,10 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { return createDiscoveryResponse(proxyRequest); } - Environment environment = Environment.from(proxyRequest.getEnvironment()); - RegionName region = RegionName.from(proxyRequest.getRegion()); + ZoneId zoneId = ZoneId.from(proxyRequest.getEnvironment(), proxyRequest.getRegion()); // Make a local copy of the list as we want to manipulate it in case of ping problems. - final List<URI> allServers = new ArrayList<>(zoneRegistry.getConfigServerUris(environment, region)); + List<URI> allServers = new ArrayList<>(zoneRegistry.getConfigServerUris(zoneId)); StringBuilder errorBuilder = new StringBuilder(); if (queueFirstServerIfDown(allServers)) { @@ -83,8 +82,8 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { ObjectMapper mapper = new ObjectMapper(); DiscoveryResponseStructure responseStructure = new DiscoveryResponseStructure(); - List<Zone> zones = zoneRegistry.zones(); - for (Zone zone : zones) { + List<ZoneId> zones = zoneRegistry.zones(); + for (ZoneId zone : zones) { if (!"".equals(proxyRequest.getEnvironment()) && !proxyRequest.getEnvironment().equals(zone.environment().value())) { continue; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index d7324450d4c..d15686077c6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -10,7 +10,7 @@ import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.TenantName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; @@ -21,7 +21,6 @@ import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.AlreadyExistsException; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; @@ -53,7 +52,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log; -import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; @@ -66,9 +64,12 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentCost; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; import com.yahoo.vespa.hosted.controller.restapi.MessageResponse; import com.yahoo.vespa.hosted.controller.restapi.Path; @@ -93,7 +94,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Scanner; -import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; @@ -241,7 +241,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler { String userIdString = request.getProperty("userOverride"); if (userIdString == null) userIdString = userFrom(request) - .orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride")); + .map(UserId::id) + .orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride")); UserId userId = new UserId(userIdString); List<Tenant> tenants = controller.tenants().asList(userId); @@ -376,13 +377,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler { // Compile version. The version that should be used when building an application object.setString("compileVersion", application.oldestDeployedVersion().orElse(controller.systemVersion()).toFullString()); - // Rotations + // Rotation Cursor globalRotationsArray = object.setArray("globalRotations"); - Set<URI> rotations = controller.getRotationUris(application.id()); - Map<String, RotationStatus> rotationHealthStatus = - rotations.isEmpty() ? Collections.emptyMap() : controller.getHealthStatus(rotations.iterator().next().getHost()); - for (URI rotation : rotations) - globalRotationsArray.addString(rotation.toString()); + application.rotation().ifPresent(rotation -> { + globalRotationsArray.addString(rotation.url().toString()); + object.setString("rotationId", rotation.id().asString()); + }); // Deployments sorted according to deployment spec List<Deployment> deployments = controller.applications().deploymentTrigger() @@ -395,8 +395,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler { deploymentObject.setString("environment", deployment.zone().environment().value()); deploymentObject.setString("region", deployment.zone().region().value()); deploymentObject.setString("instance", application.id().instance().value()); // pointless - if ( ! rotations.isEmpty()) + if (application.rotation().isPresent()) { + Map<String, RotationStatus> rotationHealthStatus = application.rotation() + .map(rotation -> controller.getHealthStatus(rotation.dnsName())) + .orElse(Collections.emptyMap()); setRotationStatus(deployment, rotationHealthStatus, deploymentObject); + } if (recurseOverDeployments(request)) // List full deployment information when recursive. toSlime(deploymentObject, new DeploymentId(application.id(), deployment.zone()), deployment, request); @@ -423,11 +427,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler { .orElseThrow(() -> new NotExistsException(id + " not found")); DeploymentId deploymentId = new DeploymentId(application.id(), - new Zone(Environment.from(environment), RegionName.from(region))); + ZoneId.from(environment, region)); - Deployment deployment = application.deployments().get(deploymentId.zone()); + Deployment deployment = application.deployments().get(deploymentId.zoneId()); if (deployment == null) - throw new NotExistsException(application + " is not deployed in " + deploymentId.zone()); + throw new NotExistsException(application + " is not deployed in " + deploymentId.zoneId()); Slime slime = new Slime(); toSlime(slime.setObject(), deploymentId, deployment, request); @@ -443,18 +447,17 @@ public class ApplicationApiHandler extends LoggingRequestHandler { serviceUrlArray.addString(uri.toString()); } - response.setString("nodes", withPath("/zone/v2/" + deploymentId.zone().environment() + "/" + deploymentId.zone().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString()); + response.setString("nodes", withPath("/zone/v2/" + deploymentId.zoneId().environment() + "/" + deploymentId.zoneId().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString()); - URI elkUrl = controller.getElkUri(deploymentId); - if (elkUrl != null) - response.setString("elkUrl", elkUrl.toString()); + controller.getLogServerUrl(deploymentId) + .ifPresent(elkUrl -> response.setString("elkUrl", elkUrl.toString())); response.setString("yamasUrl", monitoringSystemUri(deploymentId).toString()); response.setString("version", deployment.version().toFullString()); response.setString("revision", deployment.revision().id()); response.setLong("deployTimeEpochMs", deployment.at().toEpochMilli()); - Optional<Duration> deploymentTimeToLive = controller.zoneRegistry().getDeploymentTimeToLive(deploymentId.zone().environment(), deploymentId.zone().region()); - deploymentTimeToLive.ifPresent(duration -> response.setLong("expiryTimeEpochMs", deployment.at().plus(duration).toEpochMilli())); + controller.zoneRegistry().getDeploymentTimeToLive(deploymentId.zoneId()) + .ifPresent(deploymentTimeToLive -> response.setLong("expiryTimeEpochMs", deployment.at().plus(deploymentTimeToLive).toEpochMilli())); controller.applications().get(deploymentId.applicationId()).flatMap(application -> application.deploymentJobs().projectId()) .ifPresent(i -> response.setString("screwdriverId", String.valueOf(i))); @@ -489,9 +492,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private URI monitoringSystemUri(DeploymentId deploymentId) { - return controller.zoneRegistry().getMonitoringSystemUri(deploymentId.zone().environment(), - deploymentId.zone().region(), - deploymentId.applicationId()); + return controller.zoneRegistry().getMonitoringSystemUri(deploymentId); } private HttpResponse setGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region, boolean inService, HttpRequest request) { @@ -507,14 +508,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Inspector requestData = toSlime(request.getData()).get(); String reason = mandatory("reason", requestData).asString(); - String agent = authorizer.getUserId(request).toString(); + String agent = authorizer.getIdentity(request).getFullName(); long timestamp = controller.clock().instant().getEpochSecond(); EndpointStatus.Status status = inService ? EndpointStatus.Status.in : EndpointStatus.Status.out; EndpointStatus endPointStatus = new EndpointStatus(status, reason, agent, timestamp); // DeploymentId identifies the zone and application we are dealing with DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), - new Zone(Environment.from(environment), RegionName.from(region))); + ZoneId.from(environment, region)); try { List<String> rotations = controller.applications().setGlobalRotationStatus(deploymentId, endPointStatus); return new MessageResponse(String.format("Rotations %s successfully set to %s service", rotations.toString(), inService ? "in" : "out of")); @@ -526,7 +527,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse getGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region) { DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), - new Zone(Environment.from(environment), RegionName.from(region))); + ZoneId.from(environment, region)); Slime slime = new Slime(); Cursor c1 = slime.setObject().setArray("globalrotationoverride"); @@ -549,17 +550,16 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private HttpResponse rotationStatus(String tenantName, String applicationName, String instanceName, String environment, String region) { - ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName); - Set<URI> rotations = controller.getRotationUris(applicationId); - if (rotations.isEmpty()) + Application application = controller.applications().require(applicationId); + if (!application.rotation().isPresent()) { throw new NotExistsException("global rotation does not exist for '" + environment + "." + region + "'"); + } Slime slime = new Slime(); Cursor response = slime.setObject(); - Map<String, RotationStatus> rotationHealthStatus = controller.getHealthStatus(rotations.iterator().next().getHost()); - + Map<String, RotationStatus> rotationHealthStatus = controller.getHealthStatus(application.rotation().get().dnsName()); for (String rotationEndpoint : rotationHealthStatus.keySet()) { if (rotationEndpoint.contains(toDns(environment)) && rotationEndpoint.contains(toDns(region))) { Cursor bcpStatusObject = response.setObject("bcpStatus"); @@ -572,15 +572,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse waitForConvergence(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) { return new JacksonJsonResponse(controller.waitForConfigConvergence(new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), - new Zone(Environment.from(environment), RegionName.from(region))), + ZoneId.from(environment, region)), asLong(request.getProperty("timeout"), 1000))); } private HttpResponse services(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) { ApplicationView applicationView = controller.getApplicationView(tenantName, applicationName, instanceName, environment, region); - ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.from(environment), RegionName.from(region)), + ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(environment, region), new ApplicationId.Builder().tenant(tenantName).applicationName(applicationName).instanceName(instanceName).build(), - controller.getConfigServerUris(Environment.from(environment), RegionName.from(region)), + controller.getConfigServerUris(ZoneId.from(environment, region)), request.getUri()); response.setResponse(applicationView); return response; @@ -588,24 +588,24 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse service(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath, HttpRequest request) { Map<?,?> result = controller.getServiceApiResponse(tenantName, applicationName, instanceName, environment, region, serviceName, restPath); - ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.from(environment), RegionName.from(region)), + ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(environment, region), new ApplicationId.Builder().tenant(tenantName).applicationName(applicationName).instanceName(instanceName).build(), - controller.getConfigServerUris(Environment.from(environment), RegionName.from(region)), + controller.getConfigServerUris(ZoneId.from(environment, region)), request.getUri()); response.setResponse(result, serviceName, restPath); return response; } private HttpResponse createUser(HttpRequest request) { - Optional<String> username = userFrom(request); - if ( ! username.isPresent() ) throw new ForbiddenException("Not authenticated."); + Optional<UserId> user = userFrom(request); + if ( ! user.isPresent() ) throw new ForbiddenException("Not authenticated."); try { - controller.tenants().createUserTenant(username.get()); - return new MessageResponse("Created user '" + username.get() + "'"); + controller.tenants().createUserTenant(user.get().id()); + return new MessageResponse("Created user '" + user.get() + "'"); } catch (AlreadyExistsException e) { // Ok - return new MessageResponse("User '" + username + "' already exists"); + return new MessageResponse("User '" + user + "' already exists"); } } @@ -711,7 +711,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { "Active versions: " + controller.versionStatus().versions()); ApplicationId id = ApplicationId.from(tenantName, applicationName, "default"); - controller.applications().lockedOrThrow(id, application -> { + controller.applications().lockOrThrow(id, application -> { if (application.deploying().isPresent()) throw new IllegalArgumentException("Can not start a deployment of " + application + " at this time: " + application.deploying().get() + " is in progress"); @@ -729,7 +729,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { if ( ! change.isPresent()) return new MessageResponse("No deployment in progress for " + application + " at this time"); - controller.applications().lockedOrThrow(id, lockedApplication -> + controller.applications().lockOrThrow(id, lockedApplication -> controller.applications().deploymentTrigger().cancelChange(id)); return new MessageResponse("Cancelled " + change.get() + " for " + application); @@ -738,12 +738,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler { /** Schedule restart of deployment, or specific host in a deployment */ private HttpResponse restart(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) { DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), - new Zone(Environment.from(environment), RegionName.from(region))); + ZoneId.from(environment, region)); // TODO: Propagate all filters - if (request.getProperty("hostname") != null) - controller.applications().restartHost(deploymentId, new Hostname(request.getProperty("hostname"))); - else - controller.applications().restart(deploymentId); + Optional<Hostname> hostname = Optional.ofNullable(request.getProperty("hostname")).map(Hostname::new); + controller.applications().restart(deploymentId, hostname); // TODO: Change to return JSON return new StringResponse("Requested restart of " + path(TenantResource.API_PATH, tenantName, @@ -761,7 +759,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse log(String tenantName, String applicationName, String instanceName, String environment, String region) { try { DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), - new Zone(Environment.from(environment), RegionName.from(region))); + ZoneId.from(environment, region)); return new JacksonJsonResponse(controller.grabLog(deploymentId)); } catch (RuntimeException e) { @@ -773,7 +771,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse deploy(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) { ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName); - Zone zone = new Zone(Environment.from(environment), RegionName.from(region)); + ZoneId zone = ZoneId.from(environment, region); Map<String, byte[]> dataParts = new MultipartParser().parse(request); if ( ! dataParts.containsKey("deployOptions")) @@ -783,17 +781,17 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Inspector deployOptions = SlimeUtils.jsonToSlime(dataParts.get("deployOptions")).get(); + ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get("applicationZip")); DeployAuthorizer deployAuthorizer = new DeployAuthorizer(controller.zoneRegistry(), athenzClientFactory); Tenant tenant = controller.tenants().tenant(new TenantId(tenantName)).orElseThrow(() -> new NotExistsException(new TenantId(tenantName))); Principal principal = authorizer.getPrincipal(request); - deployAuthorizer.throwIfUnauthorizedForDeploy(principal, Environment.from(environment), tenant, applicationId); + deployAuthorizer.throwIfUnauthorizedForDeploy(principal, Environment.from(environment), tenant, applicationId, applicationPackage); // TODO: get rid of the json object DeployOptions deployOptionsJsonClass = new DeployOptions(screwdriverBuildJobFromSlime(deployOptions.field("screwdriverBuildJob")), optional("vespaVersion", deployOptions).map(Version::new), deployOptions.field("ignoreValidationErrors").asBool(), deployOptions.field("deployCurrentVersion").asBool()); - ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get("applicationZip")); controller.applications().validate(applicationPackage.deploymentSpec()); ActivateResult result = controller.applications().deployApplication(applicationId, zone, @@ -824,7 +822,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse deactivate(String tenantName, String applicationName, String instanceName, String environment, String region) { Application application = controller.applications().require(ApplicationId.from(tenantName, applicationName, instanceName)); - Zone zone = new Zone(Environment.from(environment), RegionName.from(region)); + ZoneId zone = ZoneId.from(environment, region); Deployment deployment = application.deployments().get(zone); if (deployment == null) { // Attempt to deactivate application even if the deployment is not known by the controller @@ -873,8 +871,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } } - private Optional<String> userFrom(HttpRequest request) { - return authorizer.getPrincipalIfAny(request).map(Principal::getName); + private Optional<UserId> userFrom(HttpRequest request) { + return authorizer.getPrincipalIfAny(request) + .map(AthenzPrincipal::getIdentity) + .filter(AthenzUser.class::isInstance) + .map(AthenzUser.class::cast) + .map(AthenzUser::getUserId); } private void toSlime(Cursor object, Tenant tenant, HttpRequest request, boolean listApplications) { @@ -985,18 +987,22 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private void throwIfNotSuperUserOrPartOfOpsDbGroup(UserGroup userGroup, HttpRequest request) { - UserId userId = authorizer.getUserId(request); - if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(userId, userGroup) ) { + AthenzIdentity identity = authorizer.getIdentity(request); + if (!(identity instanceof AthenzUser)) { + throw new ForbiddenException("Identity not an user: " + identity.getFullName()); + } + AthenzUser user = (AthenzUser) identity; + if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(user.getUserId(), userGroup) ) { throw new ForbiddenException(String.format("User '%s' is not super user or part of the OpsDB user group '%s'", - userId.id(), userGroup.id())); + user.getUserId().id(), userGroup.id())); } } private void throwIfNotAthenzDomainAdmin(AthenzDomain tenantDomain, HttpRequest request) { - UserId userId = authorizer.getUserId(request); - if ( ! authorizer.isAthenzDomainAdmin(userId, tenantDomain)) { + AthenzIdentity identity = authorizer.getIdentity(request); + if ( ! authorizer.isAthenzDomainAdmin(identity, tenantDomain)) { throw new ForbiddenException( - String.format("The user '%s' is not admin in Athenz domain '%s'", userId.id(), tenantDomain.id())); + String.format("The user '%s' is not admin in Athenz domain '%s'", identity.getFullName(), tenantDomain.id())); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java index 0c808e30c2a..b7080a763f0 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java @@ -10,16 +10,17 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; import com.yahoo.vespa.hosted.controller.common.ContextAttributes; import com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter; import javax.ws.rs.ForbiddenException; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.SecurityContext; -import java.security.Principal; import java.util.Optional; import java.util.logging.Logger; @@ -54,27 +55,26 @@ public class Authorizer { Optional<Tenant> tenant = controller.tenants().tenant(tenantId); if ( ! tenant.isPresent()) return; - UserId userId = getUserId(request); - if (isTenantAdmin(userId, tenant.get())) return; + AthenzIdentity identity = getIdentity(request); + if (isTenantAdmin(identity, tenant.get())) return; - throw loggedForbiddenException("User " + userId + " does not have write access to tenant " + tenantId); + throw loggedForbiddenException("User " + identity.getFullName() + " does not have write access to tenant " + tenantId); } - public UserId getUserId(HttpRequest request) { - String name = getPrincipal(request).getName(); - if (name == null) - throw loggedForbiddenException("Not authorized: User name is null"); - return new UserId(name); + public AthenzIdentity getIdentity(HttpRequest request) { + return getPrincipal(request).getIdentity(); } /** Returns the principal or throws forbidden */ // TODO: Avoid REST exceptions - public Principal getPrincipal(HttpRequest request) { + public AthenzPrincipal getPrincipal(HttpRequest request) { return getPrincipalIfAny(request).orElseThrow(() -> Authorizer.loggedForbiddenException("User is not authenticated")); } /** Returns the principal if there is any */ - public Optional<Principal> getPrincipalIfAny(HttpRequest request) { - return securityContextOf(request).map(SecurityContext::getUserPrincipal); + public Optional<AthenzPrincipal> getPrincipalIfAny(HttpRequest request) { + return securityContextOf(request) + .map(SecurityContext::getUserPrincipal) + .map(AthenzPrincipal.class::cast); } public Optional<NToken> getNToken(HttpRequest request) { @@ -93,26 +93,36 @@ public class Authorizer { return new ForbiddenException(formattedMessage); } - private boolean isTenantAdmin(UserId userId, Tenant tenant) { + private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) { switch (tenant.tenantType()) { case ATHENS: - return isAthenzTenantAdmin(userId, tenant.getAthensDomain().get()); - case OPSDB: - return isGroupMember(userId, tenant.getUserGroup().get()); - case USER: - return isUserTenantOwner(tenant.getId(), userId); + return isAthenzTenantAdmin(identity, tenant.getAthensDomain().get()); + case OPSDB: { + if (!(identity instanceof AthenzUser)) { + return false; + } + AthenzUser user = (AthenzUser) identity; + return isGroupMember(user.getUserId(), tenant.getUserGroup().get()); + } + case USER: { + if (!(identity instanceof AthenzUser)) { + return false; + } + AthenzUser user = (AthenzUser) identity; + return isUserTenantOwner(tenant.getId(), user.getUserId()); + } } throw new IllegalArgumentException("Unknown tenant type: " + tenant.tenantType()); } - private boolean isAthenzTenantAdmin(UserId userId, AthenzDomain tenantDomain) { + private boolean isAthenzTenantAdmin(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain) { return athenzClientFactory.createZmsClientWithServicePrincipal() - .hasTenantAdminAccess(AthenzUtils.createPrincipal(userId), tenantDomain); + .hasTenantAdminAccess(athenzIdentity, tenantDomain); } - public boolean isAthenzDomainAdmin(UserId userId, AthenzDomain tenantDomain) { + public boolean isAthenzDomainAdmin(AthenzIdentity identity, AthenzDomain tenantDomain) { return athenzClientFactory.createZmsClientWithServicePrincipal() - .isDomainAdmin(AthenzUtils.createPrincipal(userId), tenantDomain); + .isDomainAdmin(identity, tenantDomain); } public boolean isGroupMember(UserId userId, UserGroup userGroup) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java index 71126259417..c7e03048ec8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java @@ -6,15 +6,17 @@ import com.yahoo.config.provision.Environment; import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.ZmsException; +import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException; import javax.ws.rs.ForbiddenException; import javax.ws.rs.NotAuthorizedException; import java.security.Principal; +import java.util.Objects; import java.util.logging.Logger; import static com.yahoo.vespa.hosted.controller.restapi.application.Authorizer.environmentRequiresAuthorization; @@ -38,7 +40,21 @@ public class DeployAuthorizer { public void throwIfUnauthorizedForDeploy(Principal principal, Environment environment, Tenant tenant, - ApplicationId applicationId) { + ApplicationId applicationId, + ApplicationPackage applicationPackage) { + // Validate that domain in identity configuration (deployment.xml) is same as tenant domain + applicationPackage.deploymentSpec().athenzDomain().ifPresent(identityDomain -> { + AthenzDomain tenantDomain = tenant.getAthensDomain().orElseThrow(() -> new IllegalArgumentException("Identity provider only available to Athenz onboarded tenants")); + if (! Objects.equals(tenantDomain.id(), identityDomain.value())) { + throw new ForbiddenException( + String.format( + "Athenz domain in deployment.xml: [%s] must match tenant domain: [%s]", + identityDomain.value(), + tenantDomain.id() + )); + } + }); + if (!environmentRequiresAuthorization(environment)) { return; } @@ -70,7 +86,7 @@ public class DeployAuthorizer { "Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " + "Either the application has not been created at " + zoneRegistry.getDashboardUri() + " or " + "'%1$s' is not added to the application's deployer role in Athenz domain '%3$s'.", - athenzPrincipal.toYRN(), applicationId, tenantDomain.id()); + athenzPrincipal.getIdentity().getFullName(), applicationId, tenantDomain.id()); } } } @@ -91,7 +107,7 @@ public class DeployAuthorizer { try { return athenzClientFactory.createZmsClientWithServicePrincipal() .hasApplicationAccess( - principal, + principal.getIdentity(), ApplicationAction.deploy, domain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(applicationId.application().value())); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java index 6a448e475c5..0b0a2c3ad52 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponse.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.restapi.application; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.slime.Cursor; import com.yahoo.slime.JsonFormat; @@ -30,7 +30,7 @@ import java.util.regex.Pattern; */ class ServiceApiResponse extends HttpResponse { - private final Zone zone; + private final ZoneId zone; private final ApplicationId application; private final List<URI> configServerURIs; private final Slime slime; @@ -40,7 +40,7 @@ class ServiceApiResponse extends HttpResponse { private String serviceName = null; private String restPath = null; - public ServiceApiResponse(Zone zone, ApplicationId application, List<URI> configServerURIs, URI requestUri) { + public ServiceApiResponse(ZoneId zone, ApplicationId application, List<URI> configServerURIs, URI requestUri) { super(200); this.zone = zone; this.application = application; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java index e350b98adb9..4d4f01bc1a6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java @@ -12,9 +12,7 @@ import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.LockedApplication; import com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport; @@ -109,7 +107,7 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler { .orElse(JobType.component); ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default"); - controller.applications().lockedOrThrow(applicationId, application -> { + controller.applications().lockOrThrow(applicationId, application -> { // Since this is a manual operation we likely want it to trigger as soon as possible so we add it at to the // front of the queue application = controller.applications().deploymentTrigger().triggerAllowParallel( diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java index 3a3fd445bcf..83b725ae4c4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; @@ -70,7 +70,7 @@ public class ZoneApiHandler extends LoggingRequestHandler { private HttpResponse root(HttpRequest request) { List<Environment> environments = zoneRegistry.zones().stream() - .map(Zone::environment) + .map(ZoneId::environment) .distinct() .sorted(Comparator.comparing(Environment::value)) .collect(Collectors.toList()); @@ -89,7 +89,7 @@ public class ZoneApiHandler extends LoggingRequestHandler { } private HttpResponse environment(HttpRequest request, Environment environment) { - List<Zone> zones = zoneRegistry.zones().stream() + List<ZoneId> zones = zoneRegistry.zones().stream() .filter(zone -> zone.environment() == environment) .collect(Collectors.toList()); Slime slime = new Slime(); @@ -109,9 +109,7 @@ public class ZoneApiHandler extends LoggingRequestHandler { private HttpResponse defaultRegion(HttpRequest request, Environment environment) { RegionName region = zoneRegistry.getDefaultRegion(environment) - .orElseThrow(() -> new IllegalArgumentException( - "No default region for environment: " + environment - )); + .orElseThrow(() -> new IllegalArgumentException("No default region for environment: " + environment)); Slime slime = new Slime(); Cursor root = slime.setObject(); root.setString("name", region.value()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java index 529b2b25785..772dd1f6cb1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v2; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; @@ -76,14 +76,12 @@ public class ZoneApiHandler extends LoggingRequestHandler { private HttpResponse proxy(HttpRequest request) { Path path = new Path(request.getUri().getPath()); - if (!path.matches("/zone/v2/{environment}/{region}/{*}")) { + if ( ! path.matches("/zone/v2/{environment}/{region}/{*}")) { return notFound(path); } - Environment environment = Environment.from(path.get("environment")); - RegionName region = RegionName.from(path.get("region")); - Optional<Zone> zone = zoneRegistry.getZone(environment, region); - if (!zone.isPresent()) { - throw new IllegalArgumentException("No such zone: " + environment.value() + "." + region.value()); + ZoneId zoneId = ZoneId.from(path.get("environment"), path.get("region")); + if ( ! zoneRegistry.hasZone(zoneId)) { + throw new IllegalArgumentException("No such zone: " + zoneId.value()); } try { return proxy.handle(new ProxyRequest(request, "/zone/v2/")); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java new file mode 100644 index 00000000000..a638756a600 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/Rotation.java @@ -0,0 +1,49 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.rotation; + +import java.util.Objects; + +/** + * Represents a global routing rotation. + * + * @author mpolden + */ +public class Rotation { + + private final RotationId id; + private final String name; + + public Rotation(RotationId id, String name) { + this.id = Objects.requireNonNull(id); + this.name = Objects.requireNonNull(name); + } + + /** The ID of the allocated rotation. This value is generated by global routing system */ + public RotationId id() { + return id; + } + + /** The global rotation FQDN */ + public String name() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Rotation)) return false; + final Rotation rotation = (Rotation) o; + return id().equals(rotation.id()) && name().equals(rotation.name()); + } + + @Override + public int hashCode() { + return Objects.hash(id(), name()); + } + + @Override + public String toString() { + return String.format("rotation %s -> %s", id().asString(), name()); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java new file mode 100644 index 00000000000..10b15488f6e --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationId.java @@ -0,0 +1,42 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.rotation; + +import java.util.Objects; + +/** + * ID of a global rotation. + * + * @author mpolden + */ +public class RotationId { + + private final String id; + + public RotationId(String id) { + this.id = id; + } + + /** Rotation ID, e.g. rotation-42.vespa.global.routing */ + public String asString() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RotationId that = (RotationId) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "rotation ID " + id; + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java new file mode 100644 index 00000000000..508df263837 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationLock.java @@ -0,0 +1,25 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.rotation; + +import com.yahoo.vespa.curator.Lock; + +import java.util.Objects; + +/** + * A lock for the rotation repository. This is a type-safe wrapper for a curator lock. + * + * @author mpolden + */ +public class RotationLock implements AutoCloseable { + + private final Lock lock; + + RotationLock(Lock lock) { + this.lock = Objects.requireNonNull(lock, "lock cannot be null"); + } + + @Override + public void close() { + lock.close(); + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java new file mode 100644 index 00000000000..c0d3fd4758e --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java @@ -0,0 +1,117 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.rotation; + +import com.yahoo.config.application.api.DeploymentSpec; +import com.yahoo.config.provision.Environment; +import com.yahoo.vespa.hosted.controller.Application; +import com.yahoo.vespa.hosted.controller.ApplicationController; +import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; +import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.collectingAndThen; + +/** + * The rotation repository offers global rotations to Vespa applications. + * + * The list of rotations comes from RotationsConfig, which is set in the controller's services.xml. + * + * @author Oyvind Gronnesby + * @author mpolden + */ +public class RotationRepository { + + private static final Logger log = Logger.getLogger(RotationRepository.class.getName()); + + private final Map<RotationId, Rotation> allRotations; + private final ApplicationController applications; + private final CuratorDb curator; + + public RotationRepository(RotationsConfig rotationsConfig, ApplicationController applications, CuratorDb curator) { + this.allRotations = from(rotationsConfig); + this.applications = applications; + this.curator = curator; + } + + /** Acquire a exclusive lock for this */ + public RotationLock lock() { + return new RotationLock(curator.lockRotations()); + } + + /** + * Returns a rotation for the given application + * + * If a rotation is already assigned to the application, that rotation will be returned. + * If no rotation is assigned, return an available rotation. The caller is responsible for assigning the rotation. + * + * @param application The application requesting a rotation + * @param lock Lock which must be acquired by the caller + */ + public Rotation getRotation(Application application, RotationLock lock) { + if (application.rotation().isPresent()) { + return allRotations.get(application.rotation().get().id()); + } + if (!application.deploymentSpec().globalServiceId().isPresent()) { + throw new IllegalArgumentException("global-service-id is not set in deployment spec"); + } + long productionZones = application.deploymentSpec().zones().stream() + .filter(zone -> zone.deploysTo(Environment.prod)) + // Global rotations don't work for nodes in corp network + .filter(zone -> !isCorp(zone)) + .count(); + if (productionZones < 2) { + throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined"); + } + return findAvailableRotation(application, lock); + } + + /** + * Returns all unassigned rotations + * @param lock Lock which must be acquired by the caller + */ + public Map<RotationId, Rotation> availableRotations(@SuppressWarnings("unused") RotationLock lock) { + List<RotationId> assignedRotations = applications.asList().stream() + .filter(application -> application.rotation().isPresent()) + .map(application -> application.rotation().get().id()) + .collect(Collectors.toList()); + Map<RotationId, Rotation> unassignedRotations = new LinkedHashMap<>(this.allRotations); + assignedRotations.forEach(unassignedRotations::remove); + return Collections.unmodifiableMap(unassignedRotations); + } + + private Rotation findAvailableRotation(Application application, RotationLock lock) { + Map<RotationId, Rotation> availableRotations = availableRotations(lock); + if (availableRotations.isEmpty()) { + throw new IllegalStateException("Unable to assign global rotation to " + application.id() + + " - no rotations available"); + } + // Return first available rotation + RotationId rotation = availableRotations.keySet().iterator().next(); + log.info(String.format("Offering %s to application %s", rotation, application.id())); + return allRotations.get(rotation); + } + + private static boolean isCorp(DeploymentSpec.DeclaredZone zone) { + return zone.region().isPresent() && zone.region().get().value().contains("corp"); + } + + /** Returns a immutable map of rotation ID to rotation sorted by rotation ID */ + private static Map<RotationId, Rotation> from(RotationsConfig rotationConfig) { + return rotationConfig.rotations().entrySet().stream() + .map(entry -> new Rotation(new RotationId(entry.getKey()), entry.getValue().trim())) + .sorted(Comparator.comparing(rotation -> rotation.id().asString())) + .collect(collectingAndThen(Collectors.toMap(Rotation::id, + rotation -> rotation, + (k, v) -> v, + LinkedHashMap::new), + Collections::unmodifiableMap)); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java index d152cf80472..876bd5fe029 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList; import com.yahoo.collections.ListMap; import com.yahoo.component.Version; import com.yahoo.component.Vtag; +import com.yahoo.config.provision.RegionName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.github.GitSha; @@ -119,15 +120,17 @@ public class VersionStatus { private static ListMap<Version, String> findConfigServerVersions(Controller controller) { List<URI> configServers = controller.zoneRegistry().zones().stream() - .flatMap(zone -> controller.getConfigServerUris(zone.environment(), zone.region()).stream()) - .collect(Collectors.toList()); + // TODO: Filter properly. + .filter(zone -> ! zone.region().equals(RegionName.from("us-east-2a"))) + .flatMap(zone -> controller.getConfigServerUris(zone).stream()) + .collect(Collectors.toList()); ListMap<Version, String> versions = new ListMap<>(); for (URI configServer : configServers) versions.put(controller.applications().configserverClient().version(configServer), configServer.getHost()); return versions; } - + private static Collection<DeploymentStatistics> computeDeploymentStatistics(Set<Version> infrastructureVersions, List<Application> applications, Instant jobTimeoutLimit) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java deleted file mode 100644 index 9eef1dac70b..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepository.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.rotation; - -import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Environment; -import com.yahoo.log.LogLevel; -import com.yahoo.metrics.simple.Gauge; -import com.yahoo.metrics.simple.MetricReceiver; -import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId; -import com.yahoo.vespa.hosted.controller.api.ApplicationAlias; -import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; -import com.yahoo.vespa.hosted.controller.api.rotation.Rotation; -import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; -import org.jetbrains.annotations.NotNull; - -import java.net.URI; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -/** - * A rotation repository. - * - * @author Oyvind Gronnesby - */ -// TODO: Fold this into ApplicationController+Application -public class ControllerRotationRepository implements RotationRepository { - - private static final Logger log = Logger.getLogger(ControllerRotationRepository.class.getName()); - - private static final String REMAINING_ROTATIONS_METRIC_NAME = "remaining_rotations"; - private final Gauge remainingRotations; - - private final ControllerDb controllerDb; - private final Map<RotationId, Rotation> rotationsMap; - - public ControllerRotationRepository(RotationsConfig rotationConfig, ControllerDb controllerDb, MetricReceiver metricReceiver) { - this.controllerDb = controllerDb; - this.rotationsMap = buildRotationsMap(rotationConfig); - this.remainingRotations = metricReceiver.declareGauge(REMAINING_ROTATIONS_METRIC_NAME); - } - - private static Map<RotationId, Rotation> buildRotationsMap(RotationsConfig rotationConfig) { - return rotationConfig.rotations().entrySet().stream() - .map(entry -> { - RotationId rotationId = new RotationId(entry.getKey()); - return new Rotation(rotationId, entry.getValue().trim()); - }) - .collect(Collectors.toMap( - rotation -> rotation.rotationId, - rotation -> rotation - )); - } - - @Override - @NotNull - public Set<Rotation> getOrAssignRotation(ApplicationId applicationId, DeploymentSpec deploymentSpec) { - reportRemainingRotations(); - - Set<RotationId> rotations = controllerDb.getRotations(applicationId); - - if (rotations.size() > 1) { - log.warning(String.format("Application %s has %d > 1 rotation", applicationId, rotations.size())); - } - - if (!rotations.isEmpty()) { - return rotations.stream() - .map(rotationsMap::get) - .collect(Collectors.toSet()); - } - - if( ! deploymentSpec.globalServiceId().isPresent()) { - return Collections.emptySet(); - } - - long productionZoneCount = deploymentSpec.zones().stream() - .filter(zone -> zone.deploysTo(Environment.prod)) - .filter(zone -> ! isCorp(zone)) // Global rotations don't work for nodes in corp network - .count(); - - if (productionZoneCount >= 2) { - return assignRotation(applicationId); - } - else { - throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined"); - } - } - - private boolean isCorp(DeploymentSpec.DeclaredZone zone) { - return zone.region().isPresent() && zone.region().get().value().contains("corp"); - } - - @Override - @NotNull - public Set<URI> getRotationUris(ApplicationId applicationId) { - Set<RotationId> rotations = controllerDb.getRotations(applicationId); - if (rotations.isEmpty()) { - return Collections.emptySet(); - } - else { - ApplicationAlias applicationAlias = new ApplicationAlias(applicationId); - return Collections.singleton(applicationAlias.toHttpUri()); - } - } - - private Set<Rotation> assignRotation(ApplicationId applicationId) { - Set<RotationId> availableRotations = availableRotations(); - if (availableRotations.isEmpty()) { - String message = "Unable to assign global rotation to " - + applicationId + " - no rotations available"; - log.info(message); - throw new RuntimeException(message); - } - - for (RotationId rotationId : availableRotations) { - if (controllerDb.assignRotation(rotationId, applicationId)) { - log.info(String.format("Assigned rotation %s to application %s", rotationId, applicationId)); - Rotation rotation = this.rotationsMap.get(rotationId); - return Collections.singleton(rotation); - } - } - - log.info(String.format("Rotation: No rotations assigned with %s rotations available", availableRotations.size())); - return Collections.emptySet(); - } - - private Set<RotationId> availableRotations() { - Set<RotationId> assignedRotations = controllerDb.getRotations(); - Set<RotationId> allRotations = new HashSet<>(rotationsMap.keySet()); - allRotations.removeAll(assignedRotations); - return allRotations; - } - - private void reportRemainingRotations() { - try { - int freeRotationsCount = availableRotations().size(); - log.log(LogLevel.INFO, "Rotation: {0} global rotations remaining", freeRotationsCount); - remainingRotations.sample(freeRotationsCount); - } catch (Exception e) { - log.log(LogLevel.INFO, "Failed to report rotations metric", e); - } - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java deleted file mode 100644 index 4e333f0268b..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/MemoryRotationRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.rotation; - -import com.google.common.collect.ImmutableSet; -import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId; -import com.yahoo.vespa.hosted.controller.api.rotation.Rotation; -import org.jetbrains.annotations.NotNull; - -import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * A rotation repository backed by in-memory data structures - * - * @author bratseth - */ -public class MemoryRotationRepository implements RotationRepository { - - private final Map<ApplicationId, Set<Rotation>> rotations = new HashMap<>(); - - @NotNull - @Override - public Set<Rotation> getOrAssignRotation(ApplicationId application, DeploymentSpec deploymentSpec) { - if (rotations.containsKey(application)) { - return rotations.get(application); - } - Set<Rotation> rotations = ImmutableSet.of(new Rotation( - new RotationId("generated-by-routing-service-" + UUID.randomUUID().toString()), - "fake-global-rotation-" + application.toShortString()) - ); - this.rotations.put(application, rotations); - return rotations; - } - - @NotNull - @Override - public Set<URI> getRotationUris(ApplicationId applicationId) { - Set<Rotation> rotations = this.rotations.get(applicationId); - if (rotations == null) { - return Collections.emptySet(); - } - return rotations.stream() - .map(rotation -> URI.create("http://" + rotation.rotationName)) - .collect(Collectors.toSet()); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java deleted file mode 100644 index b1f7b33e58e..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/rotation/RotationRepository.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.rotation; - -import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.rotation.Rotation; -import org.jetbrains.annotations.NotNull; - -import java.net.URI; -import java.util.Set; - -/** - * A rotation repository assigns global rotations to Vespa applications. It does not take into account - * whether an application qualifies or not, but it assumes that each application should get only - * one. - * - * The list of rotations comes from the RotationsConfig, set in the controller's services.xml. - * Assignments are persisted with the RotationId as the primary key. When we assign the - * rotation to an application we try to put the mapping RotationId -> Application. If a - * mapping already exists for that RotationId, the assignment will fail. - * - * @author Oyvind Gronnesby - */ -public interface RotationRepository { - - // TODO: Change to use provision.ApplicationId - // TODO: Move the persistence into ControllerDb (done), and then collapse the 2 implementations and the interface into one - - /** - * If any rotations are assigned to the application, these will be returned. - * If no rotations are assigned, assign one rotation to the application and return that. - * - * @param applicationId ID of the application to get or assign rotation for - * @param deploymentSpec Spec of current application being deployed - * @return Set of rotations assigned (may be empty) - */ - @NotNull - Set<Rotation> getOrAssignRotation(ApplicationId applicationId, DeploymentSpec deploymentSpec); - - /** - * Get the external visible rotation URIs for this application. - * - * @param applicationId ID of the application to get or assign rotation for - */ - @NotNull - Set<URI> getRotationUris(ApplicationId applicationId); - -} diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def index 6d10f3dee28..068b1d353ba 100644 --- a/controller-server/src/main/resources/configdefinitions/athenz.def +++ b/controller-server/src/main/resources/configdefinitions/athenz.def @@ -17,6 +17,12 @@ domain string userAuthenticationPassThruAttribute string # TODO Remove once migrated to Okta +# Path to Athenz CA JKS trust store +athenzCaTrustStore string + +# Certificate DNS domain +certDnsDomain string + # Athenz service name for controller identity service.name string @@ -28,3 +34,6 @@ service.privateKeyVersion int # Name of Athenz service private key secret service.privateKeySecretName string + +# Expiry of service principal token and certificate +service.credentialsExpiryMinutes int default=43200 # 30 days diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java index 9228e83bbc6..bf7f19a996c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerClientMock.java @@ -22,7 +22,6 @@ import com.yahoo.vespa.serviceview.bindings.ApplicationView; import com.yahoo.vespa.serviceview.bindings.ClusterView; import com.yahoo.vespa.serviceview.bindings.ServiceView; -import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -43,16 +42,14 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS private final Map<ApplicationId, Boolean> applicationActivated = new HashMap<>(); private final Map<String, EndpointStatus> endpoints = new HashMap<>(); private final Map<URI, Version> versions = new HashMap<>(); - private Version defaultVersion = new Version(6, 1, 0); - /** The exception to throw on the next prepare run, or null to continue normally */ + private Version defaultVersion = new Version(6, 1, 0); private RuntimeException prepareException = null; - - private Optional<Version> lastPrepareVersion = Optional.empty(); + private Version lastPrepareVersion = null; /** The version given in the previous prepare call, or empty if no call has been made */ public Optional<Version> lastPrepareVersion() { - return lastPrepareVersion; + return Optional.ofNullable(lastPrepareVersion); } /** Return map of applications that may have been activated */ @@ -60,6 +57,7 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS return Collections.unmodifiableMap(applicationActivated); } + /** The exception to throw on the next prepare run, or null to continue normally */ public void throwOnNextPrepare(RuntimeException prepareException) { this.prepareException = prepareException; } @@ -71,10 +69,16 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS public Map<URI, Version> versions() { return versions; } + + /** Set the default config server version */ + public void setDefaultVersion(Version version) { + this.defaultVersion = version; + } @Override - public PreparedApplication prepare(DeploymentId deployment, DeployOptions deployOptions, Set<String> rotationCnames, Set<Rotation> rotations, byte[] content) { - lastPrepareVersion = deployOptions.vespaVersion.map(Version::new); + public PreparedApplication prepare(DeploymentId deployment, DeployOptions deployOptions, Set<String> rotationCnames, + Set<Rotation> rotations, byte[] content) { + lastPrepareVersion = deployOptions.vespaVersion.map(Version::new).orElse(null); if (prepareException != null) { RuntimeException prepareException = this.prepareException; this.prepareException = null; @@ -108,23 +112,20 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS public PrepareResponse prepareResponse() { PrepareResponse prepareResponse = new PrepareResponse(); prepareResponse.message = "foo"; - prepareResponse.configChangeActions = new ConfigChangeActions(Collections.emptyList(), Collections.emptyList()); + prepareResponse.configChangeActions = new ConfigChangeActions(Collections.emptyList(), + Collections.emptyList()); prepareResponse.tenant = new TenantId("tenant"); return prepareResponse; } }; } - - /** Set the default config server version */ - public void setDefaultVersion(Version version) { this.defaultVersion = version; } @Override public List<String> getNodeQueryHost(DeploymentId deployment, String type) { if (applicationInstances.containsKey(deployment.applicationId())) { return Collections.singletonList(applicationInstances.get(deployment.applicationId())); - } else { - return Collections.emptyList(); } + return Collections.emptyList(); } @Override @@ -151,7 +152,8 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS // Returns a canned example response @Override - public ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName, String environment, String region) { + public ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName, + String environment, String region) { ApplicationView applicationView = new ApplicationView(); ClusterView cluster = new ClusterView(); cluster.name = "cluster1"; @@ -172,7 +174,8 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS // Returns a canned example response @Override - public Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath) { + public Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName, + String environment, String region, String serviceName, String restPath) { Map<String,List<?>> root = new HashMap<>(); List<Map<?,?>> resources = new ArrayList<>(); Map<String,String> resource = new HashMap<>(); @@ -199,7 +202,7 @@ public class ConfigServerClientMock extends AbstractComponent implements ConfigS } @Override - public NodeList getNodeList(DeploymentId deployment) throws IOException { + public NodeList getNodeList(DeploymentId deployment) { NodeList list = new NodeList(); list.nodes = new ArrayList<>(); NodeList.Node hostA = new NodeList.Node(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java index cc915d4d9a1..02b33e4640a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ConfigServerProxyMock.java @@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.component.AbstractComponent; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.vespa.hosted.controller.proxy.ConfigServerRestExecutor; -import com.yahoo.vespa.hosted.controller.proxy.ProxyException; import com.yahoo.vespa.hosted.controller.proxy.ProxyRequest; import com.yahoo.vespa.hosted.controller.restapi.StringResponse; @@ -21,7 +20,7 @@ public class ConfigServerProxyMock extends AbstractComponent implements ConfigSe private volatile String requestBody = null; @Override - public HttpResponse handle(ProxyRequest proxyRequest) throws ProxyException { + public HttpResponse handle(ProxyRequest proxyRequest) { lastReceived = proxyRequest; // Copy request body as the input stream is drained once the request completes requestBody = asString(proxyRequest.getData()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index d0c1fd95427..58c74c9d6d2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -10,10 +10,8 @@ import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; -import com.yahoo.config.provision.Zone; -import com.yahoo.slime.Slime; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.config.SlimeUtils; -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus; @@ -24,7 +22,9 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; +import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.ApplicationRevision; import com.yahoo.vespa.hosted.controller.application.Change; @@ -32,12 +32,13 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType; import com.yahoo.vespa.hosted.controller.application.JobStatus; -import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.BuildSystem; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.persistence.ApplicationSerializer; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; +import com.yahoo.vespa.hosted.controller.rotation.RotationLock; import com.yahoo.vespa.hosted.controller.versions.DeploymentStatistics; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; @@ -297,10 +298,14 @@ public class ControllerTest { // staging deployment long app1ProjectId = 22; - ApplicationId app1 = tester.createAndDeploy("tenant1", "domain1", "application1", Environment.staging, app1ProjectId).id(); + ApplicationId app1 = tester.createAndDeploy("tenant1", "domain1", + "application1", Environment.staging, + app1ProjectId).id(); // pull-request deployment - uses different instance id - ApplicationId app1pr = tester.createAndDeploy("tenant1", "domain1", "application1", "default-pr1", Environment.staging, app1ProjectId, null).id(); + ApplicationId app1pr = tester.createAndDeploy("tenant1", "domain1", + "application1", "default-pr1", + Environment.staging, app1ProjectId, null).id(); assertTrue(applications.get(app1).isPresent()); assertEquals(app1, applications.get(app1).get().id()); @@ -315,6 +320,20 @@ public class ControllerTest { assertEquals(app1, applications.get(app1).get().id()); assertTrue(applications.get(app1pr).isPresent()); assertEquals(app1pr, applications.get(app1pr).get().id()); + + // Deleting application also removes PR instance + ApplicationId app2 = tester.createAndDeploy("tenant1", "domain1", + "application2", Environment.staging, + 33).id(); + tester.controller().applications().deleteApplication(app1, Optional.of(new NToken("ntoken"))); + assertEquals("All instances deleted", 0, + tester.controller().applications().asList(app1.tenant()).stream() + .filter(app -> app.id().application().equals(app1.application())) + .count()); + assertEquals("Other application survives", 1, + tester.controller().applications().asList(app1.tenant()).stream() + .filter(app -> app.id().application().equals(app2.application())) + .count()); } @Test @@ -494,7 +513,7 @@ public class ControllerTest { public void testGlobalRotations() throws IOException { // Setup tester and app def ControllerTester tester = new ControllerTester(); - Zone zone = Zone.defaultZone(); + ZoneId zone = ZoneId.from(Environment.defaultEnvironment(), RegionName.defaultName()); ApplicationId appId = tester.applicationId("tenant", "app1", "default"); DeploymentId deployId = new DeploymentId(appId, zone); @@ -523,11 +542,11 @@ public class ControllerTest { TenantId tenant = tester.createTenant("tenant1", "domain1", 11L); Application app = tester.createApplication(tenant, "app1", "default", 1); - tester.controller().applications().lockedOrThrow(app.id(), application -> { + tester.controller().applications().lockOrThrow(app.id(), application -> { application = application.withDeploying(Optional.of(new Change.VersionChange(Version.fromString("6.3")))); applications.store(application); try { - tester.deploy(app, new Zone(Environment.prod, RegionName.from("us-east-3"))); + tester.deploy(app, ZoneId.from("prod", "us-east-3")); fail("Expected exception"); } catch (IllegalArgumentException e) { assertEquals("Rejecting deployment of application 'tenant1.app1' to zone prod.us-east-3 as version change to 6.3 is not tested", e.getMessage()); @@ -601,16 +620,108 @@ public class ControllerTest { ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) + .globalServiceId("foo") .region("us-west-1") .region("us-central-1") // Two deployments should result in DNS alias being registered once .build(); tester.deployCompletely(application, applicationPackage); assertEquals(1, tester.controllerTester().nameService().records().size()); - Optional<Record> record = tester.controllerTester().nameService().findRecord(Record.Type.CNAME, "app1.tenant1.global.vespa.yahooapis.com"); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); assertTrue(record.isPresent()); - assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name()); - assertEquals("fake-global-rotation-tenant1.app1", record.get().value()); + assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString()); + assertEquals("rotation-fqdn-01.", record.get().data().asString()); + } + + @Test + public void testUpdatesExistingDnsAlias() { + DeploymentTester tester = new DeploymentTester(); + + // Application 1 is deployed and deleted + { + Application app1 = tester.createApplication("app1", "tenant1", 1, 1L); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") // Two deployments should result in DNS alias being registered once + .build(); + + tester.deployCompletely(app1, applicationPackage); + assertEquals(1, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString()); + assertEquals("rotation-fqdn-01.", record.get().data().asString()); + + // Application is deleted and rotation is unassigned + applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .allow(ValidationId.deploymentRemoval) + .build(); + tester.notifyJobCompletion(component, app1, true); + tester.deployAndNotify(app1, applicationPackage, true, systemTest); + tester.applications().deactivate(app1, ZoneId.from(Environment.test, RegionName.from("us-east-1"))); + tester.applications().deactivate(app1, ZoneId.from(Environment.staging, RegionName.from("us-east-3"))); + tester.applications().deleteApplication(app1.id(), Optional.of(new NToken("ntoken"))); + try (RotationLock lock = tester.applications().rotationRepository().lock()) { + assertTrue("Rotation is unassigned", + tester.applications().rotationRepository().availableRotations(lock) + .containsKey(new RotationId("rotation-id-01"))); + } + + // Record remains + record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + } + + // Application 2 is deployed and assigned same rotation as application 1 had before deletion + { + Application app2 = tester.createApplication("app2", "tenant2", 1, 1L); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") + .build(); + tester.deployCompletely(app2, applicationPackage); + assertEquals(2, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app2.tenant2.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("app2.tenant2.global.vespa.yahooapis.com", record.get().name().asString()); + assertEquals("rotation-fqdn-01.", record.get().data().asString()); + } + + // Application 1 is recreated, deployed and assigned a new rotation + { + Application app1 = tester.createApplication("app1", "tenant1", 1, 1L); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") + .build(); + tester.deployCompletely(app1, applicationPackage); + app1 = tester.applications().require(app1.id()); + assertEquals("rotation-id-02", app1.rotation().get().id().asString()); + + // Existing DNS record is updated to point to the newly assigned rotation + assertEquals(2, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("rotation-fqdn-02.", record.get().data().asString()); + } + } @Test @@ -626,7 +737,7 @@ public class ControllerTest { Application app = tester.createApplication("app1", "tenant1", 1, 2L); // Direct deploy is allowed when project ID is missing - Zone zone = new Zone(Environment.prod, RegionName.from("cd-us-central-1")); + ZoneId zone = ZoneId.from("prod", "cd-us-central-1"); // Same options as used in our integration tests DeployOptions options = new DeployOptions(Optional.empty(), Optional.empty(), false, false); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java index 8f9c22f8b81..52e1b3ae400 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java @@ -7,7 +7,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.TenantName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.slime.Slime; import com.yahoo.test.ManualClock; import com.yahoo.vespa.curator.Lock; @@ -41,7 +41,7 @@ import com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; -import com.yahoo.vespa.hosted.rotation.MemoryRotationRepository; +import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import java.util.Optional; @@ -63,22 +63,31 @@ public final class ControllerTester { private final GitHubMock gitHub; private final CuratorDb curator; private final MemoryNameService nameService; + private final RotationsConfig rotationsConfig; private Controller controller; public ControllerTester() { this(new MemoryControllerDb(), new AthenzDbMock(), new ManualClock(), new ConfigServerClientMock(), - new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), new MemoryNameService()); + new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), defaultRotationsConfig(), + new MemoryNameService()); } public ControllerTester(ManualClock clock) { this(new MemoryControllerDb(), new AthenzDbMock(), clock, new ConfigServerClientMock(), - new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), new MemoryNameService()); + new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), defaultRotationsConfig(), + new MemoryNameService()); + } + + public ControllerTester(RotationsConfig rotationsConfig) { + this(new MemoryControllerDb(), new AthenzDbMock(), new ManualClock(), new ConfigServerClientMock(), + new ZoneRegistryMock(), new GitHubMock(), new MockCuratorDb(), rotationsConfig, new MemoryNameService()); } private ControllerTester(ControllerDb db, AthenzDbMock athenzDb, ManualClock clock, ConfigServerClientMock configServer, ZoneRegistryMock zoneRegistry, - GitHubMock gitHub, CuratorDb curator, MemoryNameService nameService) { + GitHubMock gitHub, CuratorDb curator, RotationsConfig rotationsConfig, + MemoryNameService nameService) { this.db = db; this.athenzDb = athenzDb; this.clock = clock; @@ -87,7 +96,8 @@ public final class ControllerTester { this.gitHub = gitHub; this.curator = curator; this.nameService = nameService; - this.controller = createController(db, curator, configServer, clock, gitHub, zoneRegistry, + this.rotationsConfig = rotationsConfig; + this.controller = createController(db, curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry, athenzDb, nameService); } @@ -109,7 +119,8 @@ public final class ControllerTester { /** Create a new controller instance. Useful to verify that controller state is rebuilt from persistence */ public final void createNewController() { - controller = createController(db, curator, configServer, clock, gitHub, zoneRegistry, athenzDb, nameService); + controller = createController(db, curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry, athenzDb, + nameService); } /** Creates the given tenant and application and deploys it */ @@ -119,7 +130,7 @@ public final class ControllerTester { /** Creates the given tenant and application and deploys it */ public Application createAndDeploy(String tenantName, String domainName, String applicationName, - String instanceName, Zone zone, long projectId, Long propertyId) { + String instanceName, ZoneId zone, long projectId, Long propertyId) { TenantId tenant = createTenant(tenantName, domainName, propertyId); Application application = createApplication(tenant, applicationName, instanceName, projectId); deploy(application, zone); @@ -133,7 +144,7 @@ public final class ControllerTester { } /** Creates the given tenant and application and deploys it */ - public Application createAndDeploy(String tenantName, String domainName, String applicationName, Zone zone, long projectId, Long propertyId) { + public Application createAndDeploy(String tenantName, String domainName, String applicationName, ZoneId zone, long projectId, Long propertyId) { return createAndDeploy(tenantName, domainName, applicationName, "default", zone, projectId, propertyId); } @@ -152,11 +163,14 @@ public final class ControllerTester { return application; } - public Zone toZone(Environment environment) { + public ZoneId toZone(Environment environment) { switch (environment) { - case dev: case test: return new Zone(environment, RegionName.from("us-east-1")); - case staging: return new Zone(environment, RegionName.from("us-east-3")); - default: return new Zone(environment, RegionName.from("us-west-1")); + case dev: case test: + return ZoneId.from(environment, RegionName.from("us-east-1")); + case staging: + return ZoneId.from(environment, RegionName.from("us-east-3")); + default: + return ZoneId.from(environment, RegionName.from("us-west-1")); } } @@ -181,20 +195,20 @@ public final class ControllerTester { public Application createApplication(TenantId tenant, String applicationName, String instanceName, long projectId) { ApplicationId applicationId = applicationId(tenant.id(), applicationName, instanceName); controller().applications().createApplication(applicationId, Optional.of(TestIdentities.userNToken)); - controller().applications().lockedOrThrow(applicationId, lockedApplication -> + controller().applications().lockOrThrow(applicationId, lockedApplication -> controller().applications().store(lockedApplication.withProjectId(projectId))); return controller().applications().require(applicationId); } - public void deploy(Application application, Zone zone) { + public void deploy(Application application, ZoneId zone) { deploy(application, zone, new ApplicationPackage(new byte[0])); } - public void deploy(Application application, Zone zone, ApplicationPackage applicationPackage) { + public void deploy(Application application, ZoneId zone, ApplicationPackage applicationPackage) { deploy(application, zone, applicationPackage, false); } - public void deploy(Application application, Zone zone, ApplicationPackage applicationPackage, boolean deployCurrentVersion) { + public void deploy(Application application, ZoneId zone, ApplicationPackage applicationPackage, boolean deployCurrentVersion) { ScrewdriverId app1ScrewdriverId = new ScrewdriverId(String.valueOf(application.deploymentJobs().projectId().get())); GitRevision app1RevisionId = new GitRevision(new GitRepository("repo"), new GitBranch("master"), new GitCommit("commit1")); controller().applications().deployApplication(application.id(), @@ -214,13 +228,13 @@ public final class ControllerTester { return new LockedApplication(application, new Lock("/test", new MockCurator())); } - private static Controller createController(ControllerDb db, CuratorDb curator, + private static Controller createController(ControllerDb db, CuratorDb curator, RotationsConfig rotationsConfig, ConfigServerClientMock configServerClientMock, ManualClock clock, GitHubMock gitHubClientMock, ZoneRegistryMock zoneRegistryMock, AthenzDbMock athensDb, MemoryNameService nameService) { Controller controller = new Controller(db, curator, - new MemoryRotationRepository(), + rotationsConfig, gitHubClientMock, new MemoryEntityService(), new MockOrganization(clock), @@ -237,4 +251,13 @@ public final class ControllerTester { return controller; } + private static RotationsConfig defaultRotationsConfig() { + RotationsConfig.Builder builder = new RotationsConfig.Builder(); + for (int i = 1; i <= 10; i++) { + String id = String.format("%02d", i); + builder = builder.rotations("rotation-id-" + id, "rotation-fqdn-" + id); + } + return new RotationsConfig(builder); + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java index 355b63335c0..085819b433d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java @@ -9,9 +9,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.filter.AthenzTestUtils; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; /** * @author Tony Vaagenes @@ -34,8 +32,6 @@ public class TestIdentities { public static Tenant tenant = Tenant.createOpsDbTenant(tenantId, userGroup1, property); - public static NToken userNToken = new NToken.Builder( - "U1", AthenzUtils.createPrincipal(userId), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") - .build(); + public static NToken userNToken = new NToken("dummy"); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java index 18332942c24..2317b7bc6f1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java @@ -3,15 +3,16 @@ package com.yahoo.vespa.hosted.controller; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; -import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import java.net.URI; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -24,19 +25,19 @@ import java.util.Optional; */ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry { - private final Map<Zone, Duration> deploymentTimeToLive = new HashMap<>(); + private final Map<ZoneId, Duration> deploymentTimeToLive = new HashMap<>(); private final Map<Environment, RegionName> defaultRegionForEnvironment = new HashMap<>(); - private List<Zone> zones = new ArrayList<>(); + private List<ZoneId> zones = new ArrayList<>(); private SystemName system = SystemName.main; @Inject public ZoneRegistryMock() { - this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("corp-us-east-1"))); - this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("us-east-3"))); - this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("us-west-1"))); + this.zones.add(ZoneId.from("prod", "corp-us-east-1")); + this.zones.add(ZoneId.from("prod", "us-east-3")); + this.zones.add(ZoneId.from("prod", "us-west-1")); } - public ZoneRegistryMock setDeploymentTimeToLive(Zone zone, Duration duration) { + public ZoneRegistryMock setDeploymentTimeToLive(ZoneId zone, Duration duration) { deploymentTimeToLive.put(zone, duration); return this; } @@ -46,7 +47,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry return this; } - public ZoneRegistryMock setZones(List<Zone> zones) { + public ZoneRegistryMock setZones(List<ZoneId> zones) { this.zones = zones; return this; } @@ -62,33 +63,47 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry } @Override - public List<Zone> zones() { + public List<ZoneId> zones() { return Collections.unmodifiableList(zones); } @Override - public Optional<Zone> getZone(Environment environment, RegionName region) { - return zones().stream().filter(z -> z.environment().equals(environment) && - z.region().equals(region)).findFirst(); + public boolean hasZone(ZoneId zoneId) { + return zones.contains(zoneId); } @Override - public List<URI> getConfigServerUris(Environment environment, RegionName region) { - return getZone(environment, region) - .map(z -> URI.create(String.format("http://cfg.%s.%s.test", environment.value(), region.value()))) - .map(Collections::singletonList) - .orElse(Collections.emptyList()); + public List<URI> getConfigServerUris(ZoneId zoneId) { + return Collections.singletonList(URI.create(String.format("http://cfg.%s.test", zoneId.value()))); } @Override - public Optional<URI> getLogServerUri(Environment environment, RegionName region) { - return getZone(environment, region) - .map(z -> URI.create(String.format("http://log.%s.%s.test", environment.value(), region.value()))); + public List<URI> getConfigServerSecureUris(ZoneId zoneId) { + return Collections.singletonList(URI.create(String.format("https://cfg.%s.test:4443", zoneId.value()))); } @Override - public Optional<Duration> getDeploymentTimeToLive(Environment environment, RegionName region) { - return Optional.ofNullable(deploymentTimeToLive.get(new Zone(environment, region))); + public Optional<URI> getLogServerUri(DeploymentId deploymentId) { + if ( ! hasZone(deploymentId.zoneId())) + return Optional.empty(); + + String kibanaQuery = "/#/discover?_g=()&_a=(columns:!(_source)," + + "index:'logstash-*',interval:auto," + + "query:(query_string:(analyze_wildcard:!t,query:'" + + "HV-tenant:%22" + deploymentId.applicationId().tenant().value() + "%22%20" + + "AND%20HV-application:%22" + deploymentId.applicationId().application().value() + "%22%20" + + "AND%20HV-region:%22" + deploymentId.zoneId().region().value() + "%22%20" + + "AND%20HV-instance:%22" + deploymentId.applicationId().instance().value() + "%22%20" + + "AND%20HV-environment:%22" + deploymentId.zoneId().environment().value() + "%22'))," + + "sort:!('@timestamp',desc))"; + + URI kibanaPath = URI.create(kibanaQuery); + return Optional.of(URI.create(String.format("http://log.%s.test", deploymentId.zoneId().value())).resolve(kibanaPath)); + } + + @Override + public Optional<Duration> getDeploymentTimeToLive(ZoneId zoneId) { + return Optional.ofNullable(deploymentTimeToLive.get(zoneId)); } @Override @@ -97,9 +112,9 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry } @Override - public URI getMonitoringSystemUri(Environment environment, RegionName name, ApplicationId application) { - return URI.create("http://monitoring-system.test/?environment=" + environment.value() + "®ion=" - + name.value() + "&application=" + application.toShortString()); + public URI getMonitoringSystemUri(DeploymentId deploymentId) { + return URI.create("http://monitoring-system.test/?environment=" + deploymentId.zoneId().environment().value() + "®ion=" + + deploymentId.zoneId().region().value() + "&application=" + deploymentId.applicationId().toShortString()); } @Override diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java index 20db038485d..ffb78b7342a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java @@ -7,10 +7,10 @@ import com.yahoo.jdisc.handler.ReadableContentChannel; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; import org.junit.Before; import org.junit.Test; @@ -35,7 +35,7 @@ import static org.mockito.Mockito.when; */ public class AthenzPrincipalFilterTest { - private static final NToken NTOKEN = createDummyToken(); + private static final NToken NTOKEN = new NToken("dummy"); private static final String ATHENZ_PRINCIPAL_HEADER = "Athenz-Principal-Auth"; private NTokenValidator validator; @@ -44,13 +44,13 @@ public class AthenzPrincipalFilterTest { @Before public void before() { validator = mock(NTokenValidator.class); - principal = AthenzUtils.createPrincipal(new UserId("bob")); + principal = new AthenzPrincipal(AthenzUser.fromUserId(new UserId("bob")), NTOKEN); } @Test public void valid_ntoken_is_accepted() throws Exception { DiscFilterRequest request = mock(DiscFilterRequest.class); - when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getToken()); + when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken()); when(validator.validate(NTOKEN)).thenReturn(principal); @@ -78,7 +78,7 @@ public class AthenzPrincipalFilterTest { @Test public void invalid_token_is_unauthorized() throws Exception { DiscFilterRequest request = mock(DiscFilterRequest.class); - when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getToken()); + when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken()); when(validator.validate(NTOKEN)).thenThrow(new InvalidTokenException("Invalid token")); @@ -92,12 +92,6 @@ public class AthenzPrincipalFilterTest { assertThat(responseHandler.getResponseContent(), containsString("Invalid token")); } - private static NToken createDummyToken() { - return new NToken.Builder( - "U1", AthenzUtils.createPrincipal(new UserId("bob")), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") - .build(); - } - private static class ResponseHandlerMock implements ResponseHandler { public Response response; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java index 70ba504df03..907fabe9d75 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java @@ -1,21 +1,25 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.athenz.filter; +import com.yahoo.athenz.auth.token.PrincipalToken; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; -import com.yahoo.vespa.hosted.controller.athenz.NToken; -import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.InvalidTokenException; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.time.Instant; import java.util.Optional; -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.createPrincipal; +import static com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; import static org.junit.Assert.assertEquals; /** @@ -25,7 +29,7 @@ public class NTokenValidatorTest { private static final KeyPair TRUSTED_KEY = AthenzTestUtils.generateRsaKeypair(); private static final KeyPair UNKNOWN_KEY = AthenzTestUtils.generateRsaKeypair(); - private static final AthenzPrincipal PRINCIPAL = createPrincipal(new UserId("myuser")); + private static final AthenzIdentity IDENTITY = AthenzUser.fromUserId(new UserId("myuser")); @Rule public ExpectedException exceptionRule = ExpectedException.none(); @@ -33,15 +37,15 @@ public class NTokenValidatorTest { @Test public void valid_token_is_accepted() throws NoSuchAlgorithmException, InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0"); + NToken token = createNToken(IDENTITY, Instant.now(), TRUSTED_KEY.getPrivate(), "0"); AthenzPrincipal principal = validator.validate(token); - assertEquals("user.myuser", principal.toYRN()); + assertEquals("user.myuser", principal.getIdentity().getFullName()); } @Test public void invalid_signature_is_not_accepted() throws InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), UNKNOWN_KEY, "0"); + NToken token = createNToken(IDENTITY, Instant.now(), UNKNOWN_KEY.getPrivate(), "0"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("NToken is expired or has invalid signature"); validator.validate(token); @@ -50,7 +54,7 @@ public class NTokenValidatorTest { @Test public void expired_token_is_not_accepted() throws InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, 1234 /*long time ago*/, TRUSTED_KEY, "0"); + NToken token = createNToken(IDENTITY, Instant.ofEpochMilli(1234) /*long time ago*/, TRUSTED_KEY.getPrivate(), "0"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("NToken is expired or has invalid signature"); validator.validate(token); @@ -59,7 +63,7 @@ public class NTokenValidatorTest { @Test public void unknown_keyId_is_not_accepted() throws InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "unknown-key-id"); + NToken token = createNToken(IDENTITY, Instant.now(), TRUSTED_KEY.getPrivate(), "unknown-key-id"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("NToken has an unknown keyId"); validator.validate(token); @@ -69,7 +73,7 @@ public class NTokenValidatorTest { public void failing_to_find_key_should_throw_exception() throws InvalidTokenException { ZmsKeystore keystore = (athensService, keyId) -> { throw new RuntimeException(); }; NTokenValidator validator = new NTokenValidator(keystore); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0"); + NToken token = createNToken(IDENTITY, Instant.now(), TRUSTED_KEY.getPrivate(), "0"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("Failed to retrieve public key"); validator.validate(token); @@ -82,14 +86,17 @@ public class NTokenValidatorTest { : Optional.empty(); } - private static NToken createNToken(AthenzPrincipal principal, long issueTime, KeyPair keyPair, String keyId) { - return new NToken.Builder("U1", principal, keyPair.getPrivate(), keyId) + private static NToken createNToken(AthenzIdentity identity, Instant issueTime, PrivateKey privateKey, String keyId) { + PrincipalToken token = new PrincipalToken.Builder("U1", identity.getDomain().id(), identity.getName()) + .keyId(keyId) .salt("1234") - .hostname("host") + .host("host") .ip("1.2.3.4") - .issueTime(issueTime / 1000) + .issueTime(issueTime.getEpochSecond()) .expirationWindow(1000) .build(); + token.sign(privateKey); + return new NToken(token.getSignedToken()); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java index 72bfa238094..3311cffa078 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.provision.AthenzDomain; +import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; @@ -26,9 +28,11 @@ public class ApplicationPackageBuilder { private String upgradePolicy = null; private Environment environment = Environment.prod; + private String globalServiceId = null; private final StringBuilder environmentBody = new StringBuilder(); private final StringBuilder validationOverridesBody = new StringBuilder(); private final StringBuilder blockChange = new StringBuilder(); + private String athenzIdentityAttributes = null; private String searchDefinition = "search test { }"; public ApplicationPackageBuilder upgradePolicy(String upgradePolicy) { @@ -41,6 +45,11 @@ public class ApplicationPackageBuilder { return this; } + public ApplicationPackageBuilder globalServiceId(String globalServiceId) { + this.globalServiceId = globalServiceId; + return this; + } + public ApplicationPackageBuilder region(String regionName) { environmentBody.append(" <region active='true'>"); environmentBody.append(regionName); @@ -83,6 +92,11 @@ public class ApplicationPackageBuilder { return this; } + public ApplicationPackageBuilder athenzIdentity(AthenzDomain domain, AthenzService service) { + this.athenzIdentityAttributes = String.format("athenz-domain='%s' athenz-service='%s'", domain.value(), service.value()); + return this; + } + /** Sets the content of the search definition test.sd */ public ApplicationPackageBuilder searchDefinition(String testSearchDefinition) { this.searchDefinition = testSearchDefinition; @@ -90,7 +104,12 @@ public class ApplicationPackageBuilder { } private byte[] deploymentSpec() { - StringBuilder xml = new StringBuilder("<deployment version='1.0'>\n"); + StringBuilder xml = new StringBuilder(); + xml.append("<deployment version='1.0' "); + if(athenzIdentityAttributes != null) { + xml.append(athenzIdentityAttributes); + } + xml.append(">\n"); if (upgradePolicy != null) { xml.append("<upgrade policy='"); xml.append(upgradePolicy); @@ -99,6 +118,11 @@ public class ApplicationPackageBuilder { xml.append(blockChange); xml.append(" <"); xml.append(environment.value()); + if (globalServiceId != null) { + xml.append(" global-service-id='"); + xml.append(globalServiceId); + xml.append("'"); + } xml.append(">\n"); xml.append(environmentBody); xml.append(" </"); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java index 2dc6471effb..a58d2d0fa39 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.integration; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import java.util.HashMap; import java.util.Map; @@ -18,12 +18,12 @@ public class MockMetricsService implements com.yahoo.vespa.hosted.controller.api } @Override - public DeploymentMetrics getDeploymentMetrics(ApplicationId application, Zone zone) { + public DeploymentMetrics getDeploymentMetrics(ApplicationId application, ZoneId zone) { return new DeploymentMetrics(1, 2, 3, 4, 5); } @Override - public Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, Zone zone) { + public Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, ZoneId zone) { Map<String, SystemMetrics> result = new HashMap<>(); SystemMetrics system = new SystemMetrics(55.54, 69.90, 34.59); result.put("default", system); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java index ef0b05f9bb2..47d62f93def 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.Deployment; @@ -35,7 +35,7 @@ public class DeploymentExpirerTest { @Test public void testDeploymentExpiry() throws IOException, InterruptedException { tester.controllerTester().zoneRegistry().setDeploymentTimeToLive( - new Zone(Environment.dev, RegionName.from("us-east-1")), + ZoneId.from(Environment.dev, RegionName.from("us-east-1")), Duration.ofDays(14) ); DeploymentExpirer expirer = new DeploymentExpirer(tester.controller(), Duration.ofDays(10), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java new file mode 100644 index 00000000000..8647b87133e --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java @@ -0,0 +1,77 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.maintenance; + +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; +import com.yahoo.vespa.hosted.controller.Application; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; +import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; +import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; +import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; +import org.junit.Ignore; +import org.junit.Test; + +import java.time.Duration; +import java.util.Optional; + +import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType.component; +import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType.systemTest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author mpolden + */ +public class DnsMaintainerTest { + + @Test + @Ignore // TODO: Enable once DnsMaintainer actually removes records + public void removes_record_for_unassigned_rotation() { + DeploymentTester tester = new DeploymentTester(); + Application application = tester.createApplication("app1", "tenant1", 1, 1L); + + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .globalServiceId("foo") + .region("us-west-1") + .region("us-central-1") + .build(); + + // Deploy application + tester.deployCompletely(application, applicationPackage); + assertEquals(1, tester.controllerTester().nameService().records().size()); + Optional<Record> record = tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com") + ); + assertTrue(record.isPresent()); + assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString()); + assertEquals("rotation-fqdn-01.", record.get().data().asString()); + + // Remove application + applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .allow(ValidationId.deploymentRemoval) + .build(); + tester.notifyJobCompletion(component, application, true); + tester.deployAndNotify(application, applicationPackage, true, systemTest); + tester.applications().deactivate(application, ZoneId.from(Environment.test, RegionName.from("us-east-1"))); + tester.applications().deactivate(application, ZoneId.from(Environment.staging, RegionName.from("us-east-3"))); + tester.applications().deleteApplication(application.id(), Optional.of(new NToken("ntoken"))); + + // DnsMaintainer removes record + DnsMaintainer dnsMaintainer = new DnsMaintainer(tester.controller(), Duration.ofHours(12), + new JobControl(new MockCuratorDb()), + tester.controllerTester().nameService()); + dnsMaintainer.maintain(); + assertFalse("DNS record removed", tester.controllerTester().nameService().findRecord( + Record.Type.CNAME, RecordName.from("app1.tenant1.global.vespa.yahooapis.com")).isPresent()); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java index 8839f6a5a18..ac282422c89 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.component.Version; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ControllerTester; @@ -692,7 +692,7 @@ public class UpgraderTest { // Dev deployment which should be ignored Application dev0 = tester.createApplication("dev0", "tenant1", 7, 1L); - tester.controllerTester().deploy(dev0, new Zone(Environment.dev, RegionName.from("dev-region"))); + tester.controllerTester().deploy(dev0, ZoneId.from(Environment.dev, RegionName.from("dev-region"))); // New version is released and canaries upgrade version = Version.fromString("5.1"); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java index 2c1471b29b6..b281b513f4a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java @@ -8,7 +8,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; @@ -25,6 +25,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; +import com.yahoo.vespa.hosted.controller.rotation.RotationId; import org.junit.Test; import java.io.IOException; @@ -47,8 +48,8 @@ public class ApplicationSerializerTest { private static final ApplicationSerializer applicationSerializer = new ApplicationSerializer(); - private static final Zone zone1 = new Zone(Environment.from("prod"), RegionName.from("us-west-1")); - private static final Zone zone2 = new Zone(Environment.from("prod"), RegionName.from("us-east-3")); + private static final ZoneId zone1 = ZoneId.from("prod", "us-west-1"); + private static final ZoneId zone2 = ZoneId.from("prod", "us-east-3"); @Test public void testSerialization() { @@ -86,7 +87,8 @@ public class ApplicationSerializerTest { Optional.of(new Change.VersionChange(Version.fromString("6.7"))), true, Optional.of(IssueId.from("1234")), - new MetricsService.ApplicationMetrics(0.5, 0.9)); + new MetricsService.ApplicationMetrics(0.5, 0.9), + Optional.of(new RotationId("my-rotation"))); Application serialized = applicationSerializer.fromSlime(applicationSerializer.toSlime(original)); @@ -115,6 +117,7 @@ public class ApplicationSerializerTest { assertEquals(original.ownershipIssueId(), serialized.ownershipIssueId()); assertEquals(original.deploying(), serialized.deploying()); + assertEquals(original.rotation().get().id(), serialized.rotation().get().id()); // Test cluster utilization assertEquals(0, serialized.deployments().get(zone1).clusterUtils().size()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java index 189b3a97a80..c668bde0d40 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; public class VersionStatusSerializerTest { @Test - public void testSerialization() throws Exception { + public void testSerialization() { List<VespaVersion> vespaVersions = new ArrayList<>(); DeploymentStatistics statistics = new DeploymentStatistics( Version.fromString("5.0"), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java index 6c5120df515..f3fa1e21eda 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java @@ -5,6 +5,7 @@ import com.yahoo.application.container.JDisc; import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TestIdentities; @@ -20,12 +21,13 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; +import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.maintenance.JobControl; import com.yahoo.vespa.hosted.controller.maintenance.Upgrader; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; @@ -44,18 +46,16 @@ import java.util.Optional; public class ContainerControllerTester { private final ContainerTester containerTester; - private final Controller controller; private final Upgrader upgrader; public ContainerControllerTester(JDisc container, String responseFilePath) { containerTester = new ContainerTester(container, responseFilePath); - controller = (Controller)container.components().getComponent("com.yahoo.vespa.hosted.controller.Controller"); CuratorDb curatorDb = new MockCuratorDb(); curatorDb.writeUpgradesPerMinute(100); - upgrader = new Upgrader(controller, Duration.ofDays(1), new JobControl(curatorDb), curatorDb); + upgrader = new Upgrader(controller(), Duration.ofDays(1), new JobControl(curatorDb), curatorDb); } - public Controller controller() { return controller; } + public Controller controller() { return containerTester.controller(); } public Upgrader upgrader() { return upgrader; } @@ -69,18 +69,18 @@ public class ContainerControllerTester { public Application createApplication(String athensDomain, String tenant, String application) { AthenzDomain domain1 = addTenantAthenzDomain(athensDomain, "mytenant"); - controller.tenants().addTenant(Tenant.createAthensTenant(new TenantId(tenant), domain1, + controller().tenants().addTenant(Tenant.createAthensTenant(new TenantId(tenant), domain1, new Property("property1"), Optional.of(new PropertyId("1234"))), Optional.of(TestIdentities.userNToken)); ApplicationId app = ApplicationId.from(tenant, application, "default"); - return controller.applications().createApplication(app, Optional.of(TestIdentities.userNToken)); + return controller().applications().createApplication(app, Optional.of(TestIdentities.userNToken)); } - public Application deploy(Application application, ApplicationPackage applicationPackage, Zone zone, long projectId) { + public Application deploy(Application application, ApplicationPackage applicationPackage, ZoneId zone, long projectId) { ScrewdriverId app1ScrewdriverId = new ScrewdriverId(String.valueOf(projectId)); GitRevision app1RevisionId = new GitRevision(new GitRepository("repo"), new GitBranch("master"), new GitCommit("commit1")); - controller.applications().deployApplication(application.id(), + controller().applications().deployApplication(application.id(), zone, applicationPackage, new DeployOptions(Optional.of(new ScrewdriverBuildJob(app1ScrewdriverId, app1RevisionId)), Optional.empty(), false, false)); @@ -106,7 +106,7 @@ public class ContainerControllerTester { AthenzDomain athensDomain = new AthenzDomain(domainName); AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain); domain.markAsVespaTenant(); - domain.admin(new AthenzPrincipal(new AthenzDomain("domain"), new UserId(userName))); + domain.admin(AthenzUtils.createAthenzIdentity(new AthenzDomain("domain"), userName)); mock.getSetup().addDomain(domain); return athensDomain; } @@ -121,4 +121,17 @@ public class ContainerControllerTester { containerTester.assertResponse(request, expectedResponse, expectedStatusCode); } + /* + * Authorize action on tenantDomain/application for a given screwdriverId + */ + public void authorize(AthenzDomain tenantDomain, ScrewdriverId screwdriverId, ApplicationAction action, Application application) { + AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components() + .getComponent(AthenzClientFactoryMock.class.getName()); + + mock.getSetup() + .domains.get(tenantDomain) + .applications.get(new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(application.id().application().value())) + .addRoleMember(action, AthenzService.fromScrewdriverId(screwdriverId)); + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java index c0e8b48f821..95810e90cdb 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java @@ -43,14 +43,16 @@ public class ContainerTester { public JDisc container() { return container; } + public Controller controller() { + return (Controller) container.components().getComponent(Controller.class.getName()); + } + public void updateSystemVersion() { - Controller controller = (Controller)container.components().getComponent("com.yahoo.vespa.hosted.controller.Controller"); - controller.updateVersionStatus(VersionStatus.compute(controller)); + controller().updateVersionStatus(VersionStatus.compute(controller())); } public void updateSystemVersion(Version version) { - Controller controller = (Controller)container.components().getComponent("com.yahoo.vespa.hosted.controller.Controller"); - controller.updateVersionStatus(VersionStatus.compute(controller, version)); + controller().updateVersionStatus(VersionStatus.compute(controller(), version)); } public void assertResponse(Supplier<Request> request, File responseFile) throws IOException { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 044c5d75d12..631ceab98a5 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -25,60 +25,70 @@ import static org.junit.Assert.assertEquals; public class ControllerContainerTest { protected JDisc container; + @Before public void startContainer() { container = JDisc.fromServicesXml(controllerServicesXml, Networking.disable); } + @After public void stopContainer() { container.close(); } private final String controllerServicesXml = - "<jdisc version='1.0'>" + - " <config name='vespa.hosted.zone.config.zone'>" + - " <system>main</system>" + - " </config>" + - " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>" + - " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues'/>" + - " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization'/>" + - " <component id='com.yahoo.vespa.hosted.controller.ConfigServerClientMock'/>" + - " <component id='com.yahoo.vespa.hosted.controller.ZoneRegistryMock'/>" + - " <component id='com.yahoo.vespa.hosted.controller.Controller'/>" + - " <component id='com.yahoo.vespa.hosted.controller.ConfigServerProxyMock'/>" + - " <component id='com.yahoo.vespa.hosted.controller.integration.MockMetricsService'/>" + - " <component id='com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance'/>" + - " <component id='com.yahoo.vespa.hosted.controller.maintenance.JobControl'/>" + - " <component id='com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb'/>" + - " <component id='com.yahoo.vespa.hosted.controller.restapi.application.MockAuthorizer'/>" + - " <component id='com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator'/>" + - " <component id='com.yahoo.vespa.hosted.rotation.MemoryRotationRepository'/>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.RootHandler'>" + - " <binding>http://*/</binding>" + - " </handler>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiHandler'>" + - " <binding>http://*/application/v4/*</binding>" + - " </handler>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.deployment.DeploymentApiHandler'>" + - " <binding>http://*/deployment/v1/*</binding>" + - " </handler>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.controller.ControllerApiHandler'>" + - " <binding>http://*/controller/v1/*</binding>" + - " </handler>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.screwdriver.ScrewdriverApiHandler'>" + - " <binding>http://*/screwdriver/v1/*</binding>" + - " </handler>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>" + - " <binding>http://*/zone/v1</binding>" + - " <binding>http://*/zone/v1/*</binding>" + - " </handler>" + - " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v2.ZoneApiHandler'>" + - " <binding>http://*/zone/v2</binding>" + - " <binding>http://*/zone/v2/*</binding>" + - " </handler>" + + "<jdisc version='1.0'>\n" + + " <config name='vespa.hosted.zone.config.zone'>\n" + + " <system>main</system>\n" + + " </config>\n" + + " <config name=\"vespa.hosted.rotation.config.rotations\">\n" + + " <rotations>\n" + + " <item key=\"rotation-id-1\">rotation-fqdn-1</item>\n" + + " <item key=\"rotation-id-2\">rotation-fqdn-2</item>\n" + + " <item key=\"rotation-id-3\">rotation-fqdn-3</item>\n" + + " <item key=\"rotation-id-4\">rotation-fqdn-4</item>\n" + + " <item key=\"rotation-id-5\">rotation-fqdn-5</item>\n" + + " </rotations>\n" + + " </config>\n" + + " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrganization'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.ConfigServerClientMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.ZoneRegistryMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.Controller'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.ConfigServerProxyMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.integration.MockMetricsService'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.maintenance.JobControl'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.restapi.application.MockAuthorizer'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator'/>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.RootHandler'>\n" + + " <binding>http://*/</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiHandler'>\n" + + " <binding>http://*/application/v4/*</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.deployment.DeploymentApiHandler'>\n" + + " <binding>http://*/deployment/v1/*</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.controller.ControllerApiHandler'>\n" + + " <binding>http://*/controller/v1/*</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.screwdriver.ScrewdriverApiHandler'>\n" + + " <binding>http://*/screwdriver/v1/*</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>\n" + + " <binding>http://*/zone/v1</binding>\n" + + " <binding>http://*/zone/v1/*</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v2.ZoneApiHandler'>\n" + + " <binding>http://*/zone/v2</binding>\n" + + " <binding>http://*/zone/v2/*</binding>\n" + + " </handler>\n" + "</jdisc>"; protected void assertResponse(Request request, int responseStatus, String responseMessage) throws IOException { 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 bf4586f9fd0..f48f6b02bd2 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 @@ -9,6 +9,7 @@ import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ConfigServerClientMock; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; +import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; @@ -21,10 +22,12 @@ import com.yahoo.vespa.hosted.controller.application.ClusterUtilization; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; +import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; @@ -57,17 +60,23 @@ import static com.yahoo.application.container.handler.Request.Method.PUT; /** * @author bratseth * @author mpolden + * @author bjorncs */ public class ApplicationApiTest extends ControllerContainerTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/"; + private static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) + .globalServiceId("foo") .region("corp-us-east-1") + .region("us-east-3") + .region("us-west-1") .build(); - private static final String athenzUserDomain = "domain1"; - private static final String athenzScrewdriverDomain = AthenzUtils.SCREWDRIVER_DOMAIN.id(); + private static final AthenzDomain ATHENZ_TENANT_DOMAIN = new AthenzDomain("domain1"); + private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345"); + private static final UserId USER_ID = new UserId("myuser"); @Test public void testApplicationApi() throws Exception { @@ -75,7 +84,7 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerTester tester = controllerTester.containerTester(); tester.updateSystemVersion(); - addTenantAthenzDomain(athenzUserDomain, "mytenant"); // (Necessary but not provided in this API) + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // (Necessary but not provided in this API) // GET API root tester.assertResponse(request("/application/v4/", GET), @@ -91,14 +100,16 @@ public class ApplicationApiTest extends ControllerContainerTest { new File("cookiefreshness.json")); // POST (add) a tenant without property ID tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // PUT (modify) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // GET the authenticated user (with associated tenants) - tester.assertResponse(request("/application/v4/user", GET), + tester.assertResponse(request("/application/v4/user", GET).userIdentity(USER_ID), new File("user.json")); // GET all tenants tester.assertResponse(request("/application/v4/tenant/", GET), @@ -106,15 +117,17 @@ public class ApplicationApiTest extends ControllerContainerTest { // Add another Athens domain, so we can try to create more tenants - addTenantAthenzDomain("domain2", "mytenant"); // New domain to test tenant w/property ID + createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); // New domain to test tenant w/property ID // Add property info for that property id, as well, in the mock organization. addPropertyData((MockOrganization) controllerTester.controller().organization(), "1234"); // POST (add) a tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant2", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"), new File("tenant-without-applications-with-id.json")); // PUT (modify) a tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant2", PUT) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"), new File("tenant-without-applications-with-id.json")); // GET a tenant with property ID @@ -124,15 +137,18 @@ public class ApplicationApiTest extends ControllerContainerTest { // Test legacy OpsDB tenants // POST (add) an OpsDB tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant3", POST) + .userIdentity(USER_ID) .data("{\"userGroup\":\"group1\",\"property\":\"property1\",\"propertyId\":\"1234\"}"), new File("opsdb-tenant-with-id-without-applications.json")); // PUT (modify) the OpsDB tenant to set another property tester.assertResponse(request("/application/v4/tenant/tenant3", PUT) + .userIdentity(USER_ID) .data("{\"userGroup\":\"group1\",\"property\":\"property2\",\"propertyId\":\"4321\"}"), new File("opsdb-tenant-with-new-id-without-applications.json")); // POST (create) an application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); // GET a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", GET), @@ -143,11 +159,13 @@ public class ApplicationApiTest extends ControllerContainerTest { new File("application-list.json")); // POST triggering of a full deployment to an application (if version is omitted, current system version is used) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST) + .userIdentity(USER_ID) .data("6.1.0"), new File("application-deployment.json")); // DELETE (cancel) ongoing change - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE) + .userIdentity(USER_ID), new File("application-deployment-cancelled.json")); // DELETE (cancel) again is a no-op @@ -158,14 +176,16 @@ public class ApplicationApiTest extends ControllerContainerTest { HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-result.json")); // POST (deploy) an application to a zone. This simulates calls done by our tenant pipeline. ApplicationId id = ApplicationId.from("tenant1", "application1", "default"); long screwdriverProjectId = 123; - addScrewdriverUserToDomain("screwdriveruser1", "domain1"); // (Necessary but not provided in this API) + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, + ATHENZ_TENANT_DOMAIN, + new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); // (Necessary but not provided in this API) // Trigger deployment tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST) @@ -175,7 +195,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... systemtest tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default", DELETE), "Deactivated tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default"); @@ -184,7 +204,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... staging tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default", DELETE), "Deactivated tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default"); @@ -193,7 +213,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... prod zone tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, screwdriverProjectId, false, DeploymentJobs.JobType.productionCorpUsEast1); @@ -211,22 +231,22 @@ public class ApplicationApiTest extends ControllerContainerTest { addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "default")); // GET at root, with "&recursive=deployment", returns info about all tenants, their applications and their deployments tester.assertResponse(request("/application/v4/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("deployment"), new File("recursive-root.json")); // GET at root, with "&recursive=tenant", returns info about all tenants, with limmited info about their applications. tester.assertResponse(request("/application/v4/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("tenant"), new File("recursive-until-tenant-root.json")); // GET at a tenant, with "&recursive=true", returns full info about their applications and their deployments tester.assertResponse(request("/application/v4/tenant/tenant1/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("true"), new File("tenant1-recursive.json")); // GET at an application, with "&recursive=true", returns full info about its deployments tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("true"), new File("application1-recursive.json")); @@ -260,18 +280,11 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default", DELETE), "Deactivated tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default"); - // DELETE an application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE), - ""); - // DELETE a tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), - new File("tenant-without-applications.json")); - // PUT (create) the authenticated user byte[] data = new byte[0]; tester.assertResponse(request("/application/v4/user?user=newuser&domain=by", PUT) .data(data) - .domain(athenzUserDomain).user("newuser"), + .userIdentity(new UserId("newuser")), new File("create-user-response.json")); // OPTIONS return 200 OK tester.assertResponse(request("/application/v4/", Request.Method.OPTIONS), @@ -287,11 +300,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // SET global rotation override status tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation/override", PUT) + .userIdentity(USER_ID) .data("{\"reason\":\"because i can\"}"), new File("global-rotation-put.json")); // DELETE global rotation override status tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation/override", DELETE) + .userIdentity(USER_ID) .data("{\"reason\":\"because i can\"}"), new File("global-rotation-delete.json")); @@ -300,11 +315,18 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/promote", POST), "{\"message\":\"Successfully copied environment hosted-instance_tenant1_application1_placeholder_component_default to hosted-instance_tenant1_application1_us-west-1_prod_default\"}"); + // DELETE an application + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID), + ""); + // DELETE a tenant + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID), + new File("tenant-without-applications.json")); + controllerTester.controller().deconstruct(); } private void addIssues(ContainerControllerTester tester, ApplicationId id) { - tester.controller().applications().lockedOrThrow(id, application -> + tester.controller().applications().lockOrThrow(id, application -> tester.controller().applications().store(application .withDeploymentIssueId(IssueId.from("123")) .withOwnershipIssueId(IssueId.from("321")))); @@ -316,23 +338,28 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); tester.updateSystemVersion(); - addTenantAthenzDomain(athenzUserDomain, "mytenant"); - addScrewdriverUserToDomain("screwdriveruser1", "domain1"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // Create tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // Create application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); + // Grant deploy access + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, + ATHENZ_TENANT_DOMAIN, + new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1")); + // POST (deploy) an application to a prod zone - allowed when project ID is not specified HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/deploy", POST) .data(entity) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); } @@ -342,18 +369,22 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); tester.updateSystemVersion(); - addTenantAthenzDomain(athenzUserDomain, "mytenant"); - addScrewdriverUserToDomain("screwdriveruser1", "domain1"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // Create tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // Create application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); + // Give Screwdriver project deploy access + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1")); + // Deploy ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .region("us-east-3") @@ -367,12 +398,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // us-east-3 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3); // New zone is added before us-east-3 applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") // These decides the ordering of deploymentJobs and instances in the response .region("us-west-1") .region("us-east-3") @@ -383,13 +415,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // us-west-1 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsWest1); // us-east-3 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST) - .data(deployData).domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .data(deployData).screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3); @@ -402,7 +434,7 @@ public class ApplicationApiTest extends ControllerContainerTest { public void testErrorResponses() throws Exception { ContainerTester tester = new ContainerTester(container, responseFiles); tester.updateSystemVersion(); - addTenantAthenzDomain("domain1", "mytenant"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // PUT (update) non-existing tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) @@ -427,28 +459,33 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (add) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // POST (add) another tenant under the same domain tester.assertResponse(request("/application/v4/tenant/tenant2", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create tenant 'tenant2': The Athens domain 'domain1' is already connected to tenant 'tenant1'\"}", 400); // Add the same tenant again tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'tenant1' already exists\"}", 400); // POST (create) an (empty) application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); // Create the same application again - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"An application with id 'tenant1.application1' already exists\"}", + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create 'tenant1.application1': Application already exists\"}", 400); ConfigServerClientMock configServer = (ConfigServerClientMock)container.components().getComponent("com.yahoo.vespa.hosted.controller.ConfigServerClientMock"); @@ -458,44 +495,48 @@ public class ApplicationApiTest extends ControllerContainerTest { HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-failure.json"), 400); // POST (deploy) an application without available capacity configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to prepare application", ConfigServerException.ErrorCode.OUT_OF_CAPACITY, null)); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-out-of-capacity.json"), 400); // POST (deploy) an application where activation fails configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to activate application", ConfigServerException.ErrorCode.ACTIVATION_CONFLICT, null)); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-activation-conflict.json"), 409); // POST (deploy) an application where we get an internal server error configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Internal server error", ConfigServerException.ErrorCode.INTERNAL_SERVER_ERROR, null)); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-internal-server-error.json"), 500); // DELETE tenant which has an application - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) + .userIdentity(USER_ID), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not delete tenant 'tenant1': This tenant has active applications\"}", 400); // DELETE application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) + .userIdentity(USER_ID), ""); // DELETE application again - should produce 404 - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) + .userIdentity(USER_ID), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete application 'tenant1.application1': Application not found\"}", 404); // DELETE tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) + .userIdentity(USER_ID), new File("tenant-without-applications.json")); // DELETE tenant again - should produce 404 tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), @@ -511,48 +552,46 @@ public class ApplicationApiTest extends ControllerContainerTest { @Test public void testAuthorization() throws Exception { ContainerTester tester = new ContainerTester(container, responseFiles); - String authorizedUser = "mytenant"; - String unauthorizedUser = "othertenant"; + UserId authorizedUser = USER_ID; + UserId unauthorizedUser = new UserId("othertenant"); // Mutation without an authorized user is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", POST) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(null), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"FORBIDDEN\",\"message\":\"User is not authenticated\"}", 403); // ... but read methods are allowed tester.assertResponse(request("/application/v4/tenant/", GET) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(null), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "[]", 200); - addTenantAthenzDomain("domain1", "mytenant"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // Creating a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'othertenant' is not admin in Athenz domain 'domain1'\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'user.othertenant' is not admin in Athenz domain 'domain1'\"}", 403); // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), new File("tenant-without-applications.json"), 200); // Creating an application for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), new File("application-reference.json"), 200); @@ -560,44 +599,96 @@ public class ApplicationApiTest extends ControllerContainerTest { HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'mytenant' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'domain1'.\"}", + .userIdentity(USER_ID), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'user.myuser' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'user'.\"}", 403); // Deleting an application for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // (Deleting it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), "", 200); // Updating a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // Change Athens domain - addTenantAthenzDomain("domain2", "mytenant"); + createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain2\", \"property\":\"property1\"}") - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), "{\"tenant\":\"tenant1\",\"type\":\"ATHENS\",\"athensDomain\":\"domain2\",\"property\":\"property1\",\"applications\":[]}", 200); // Deleting a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); } + @Test + public void deployment_fails_on_illegal_domain_in_deployment_spec() throws IOException { + ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); + ContainerTester tester = controllerTester.containerTester(); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .upgradePolicy("default") + .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("invalid.domain"), com.yahoo.config.provision.AthenzService.from("service")) + .environment(Environment.prod) + .region("us-west-1") + .build(); + long screwdriverProjectId = 123; + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + + Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.id(), "tenant1", "application1"); + ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); + controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); + + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) + .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) + .screwdriverIdentity(screwdriverId), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Athenz domain in deployment.xml: [invalid.domain] must match tenant domain: [domain1]\"}", + 403); + + } + + @Test + public void deployment_succeeds_when_correct_domain_is_used() throws IOException { + ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); + ContainerTester tester = controllerTester.containerTester(); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .upgradePolicy("default") + .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service")) + .environment(Environment.prod) + .region("us-west-1") + .build(); + long screwdriverProjectId = 123; + ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); + + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + + Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.id(), "tenant1", "application1"); + controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); + + // Allow systemtest to succeed by notifying completion of system test + controllerTester.notifyJobCompletion(application.id(), screwdriverProjectId, true, DeploymentJobs.JobType.component); + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) + .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) + .screwdriverIdentity(screwdriverId), + new File("deploy-result.json")); + + } + private HttpEntity createApplicationDeployData(ApplicationPackage applicationPackage, Optional<Long> screwdriverJobId) { MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody("deployOptions", deployOptions(screwdriverJobId), ContentType.APPLICATION_JSON); @@ -634,8 +725,7 @@ public class ApplicationApiTest extends ControllerContainerTest { private final String path; private final Request.Method method; private byte[] data = new byte[0]; - private String domain = "domain1"; - private String user = "mytenant"; + private AthenzIdentity identity; private String contentType = "application/json"; private String recursive; @@ -655,8 +745,8 @@ public class ApplicationApiTest extends ControllerContainerTest { } return data(out.toByteArray()).contentType(data.getContentType().getValue()); } - private RequestBuilder domain(String domain) { this.domain = domain; return this; } - private RequestBuilder user(String user) { this.user = user; return this; } + private RequestBuilder userIdentity(UserId userId) { this.identity = AthenzUser.fromUserId(userId); return this; } + private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = AthenzService.fromScrewdriverId(screwdriverId); return this; } private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; } private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; } @@ -664,10 +754,13 @@ public class ApplicationApiTest extends ControllerContainerTest { public Request get() { Request request = new Request("http://localhost:8080" + path + // user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters - "?domain=" + domain + (user == null ? "" : "&user=" + user) + - (recursive == null ? "" : "&recursive=" + recursive), + (recursive == null ? "" : "?recursive=" + recursive), data, method); request.getHeaders().put("Content-Type", contentType); + if (identity != null) { + request.getHeaders().put("Athenz-Identity-Domain", identity.getDomain().id()); + request.getHeaders().put("Athenz-Identity-Name", identity.getName()); + } return request; } } @@ -681,26 +774,27 @@ public class ApplicationApiTest extends ControllerContainerTest { * In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the * mock setup to replicate the action. */ - private AthenzDomain addTenantAthenzDomain(String domainName, String userName) { + private void createAthenzDomainWithAdmin(AthenzDomain domain, UserId userId) { AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() .getComponent(AthenzClientFactoryMock.class.getName()); - AthenzDomain athensDomain = new AthenzDomain(domainName); - AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain); - domain.markAsVespaTenant(); - domain.admin(AthenzUtils.createPrincipal(new UserId(userName))); - mock.getSetup().addDomain(domain); - return athensDomain; + AthenzDbMock.Domain domainMock = new AthenzDbMock.Domain(domain); + domainMock.markAsVespaTenant(); + domainMock.admin(AthenzUser.fromUserId(userId)); + mock.getSetup().addDomain(domainMock); } /** * In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the * mock setup to replicate the action. */ - private void addScrewdriverUserToDomain(String screwdriverUserId, String domainName) { + private void addScrewdriverUserToDeployRole(ScrewdriverId screwdriverId, + AthenzDomain domain, + com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId applicationId) { AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() .getComponent(AthenzClientFactoryMock.class.getName()); - AthenzDbMock.Domain domain = mock.getSetup().domains.get(new AthenzDomain(domainName)); - domain.admin(new AthenzPrincipal(new AthenzDomain(athenzScrewdriverDomain), new UserId(screwdriverUserId))); + AthenzIdentity screwdriverIdentity = AthenzService.fromScrewdriverId(screwdriverId); + AthenzDbMock.Application athenzApplication = mock.getSetup().domains.get(domain).applications.get(applicationId); + athenzApplication.addRoleMember(ApplicationAction.deploy, screwdriverIdentity); } private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application, long projectId, @@ -715,7 +809,7 @@ public class ApplicationApiTest extends ControllerContainerTest { application.tenant().value(), application.application().value()); tester.assertResponse(request(testPath, POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request(testPath, DELETE), "Deactivated " + testPath.replaceFirst("/application/v4/", "")); @@ -726,7 +820,7 @@ public class ApplicationApiTest extends ControllerContainerTest { application.tenant().value(), application.application().value()); tester.assertResponse(request(stagingPath, POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request(stagingPath, DELETE), "Deactivated " + stagingPath.replaceFirst("/application/v4/", "")); @@ -742,7 +836,7 @@ public class ApplicationApiTest extends ControllerContainerTest { */ private void setDeploymentMaintainedInfo(ContainerControllerTester controllerTester) { for (Application application : controllerTester.controller().applications().asList()) { - controllerTester.controller().applications().lockedOrThrow(application.id(), lockedApplication -> { + controllerTester.controller().applications().lockOrThrow(application.id(), lockedApplication -> { lockedApplication = lockedApplication.with(new ApplicationMetrics(0.5, 0.7)); for (Deployment deployment : application.deployments().values()) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java index e5898b7a593..988304be600 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java @@ -5,11 +5,11 @@ import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TestIdentities; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; -import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.NToken; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.NToken; import javax.ws.rs.core.SecurityContext; import java.security.Principal; @@ -20,6 +20,7 @@ import java.util.Optional; * This is necessary because filters are not currently executed when executing requests with Application. * * @author bratseth + * @author bjorncs */ @SuppressWarnings("unused") // injected public class MockAuthorizer extends Authorizer { @@ -30,10 +31,14 @@ public class MockAuthorizer extends Authorizer { /** Returns a principal given by the request parameters 'domain' and 'user' */ @Override - public Optional<Principal> getPrincipalIfAny(HttpRequest request) { - if (request.getProperty("user") == null) return Optional.empty(); - return Optional.of(new AthenzPrincipal(new AthenzDomain(request.getProperty("domain")), - new UserId(request.getProperty("user")))); + public Optional<AthenzPrincipal> getPrincipalIfAny(HttpRequest request) { + String domain = request.getHeader("Athenz-Identity-Domain"); + String name = request.getHeader("Athenz-Identity-Name"); + if (domain == null || name == null) return Optional.empty(); + return Optional.of( + new AthenzPrincipal( + AthenzUtils.createAthenzIdentity(new AthenzDomain(domain), name), + new NToken("dummy"))); } /** Returns the hardcoded NToken of {@link TestIdentities#userId} */ @@ -42,12 +47,6 @@ public class MockAuthorizer extends Authorizer { return Optional.of(TestIdentities.userNToken); } - private static class MockPrincipal implements Principal { - - @Override - public String getName() { return TestIdentities.userId.id(); } - - } @Override protected Optional<SecurityContext> securityContextOf(HttpRequest request) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java index 6cf90905679..4c25bf6fe61 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ServiceApiResponseTest.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.restapi.application; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.io.IOUtils; import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; @@ -32,10 +32,10 @@ public class ServiceApiResponseTest { @Test public void testServiceViewResponse() throws URISyntaxException, IOException { - ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.prod, RegionName.from("us-west-1")), - ApplicationId.from("tenant1", "application1", "default"), - Collections.singletonList(new URI("config-server1")), - new URI("http://server1:4080/request/path?foo=bar")); + ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(Environment.prod, RegionName.from("us-west-1")), + ApplicationId.from("tenant1", "application1", "default"), + Collections.singletonList(new URI("config-server1")), + new URI("http://server1:4080/request/path?foo=bar")); ApplicationView applicationView = new ApplicationView(); ClusterView clusterView = new ClusterView(); clusterView.type = "container"; @@ -63,7 +63,7 @@ public class ServiceApiResponseTest { @Test public void testServiceViewResponseWithURLs() throws URISyntaxException, IOException { - ServiceApiResponse response = new ServiceApiResponse(new Zone(Environment.prod, RegionName.from("us-west-1")), + ServiceApiResponse response = new ServiceApiResponse(ZoneId.from(Environment.prod, RegionName.from("us-west-1")), ApplicationId.from("tenant2", "application2", "default"), Collections.singletonList(new URI("http://cfg1.test/")), new URI("http://cfg1.test/serviceview/v1/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/service/searchnode-9dujk1pa0vufxrj6n4yvmi8uc/state/v1")); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json index 6442ddf5c02..e46c755e8bf 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json @@ -205,8 +205,9 @@ ], "compileVersion": "(ignore)", "globalRotations": [ - "http://fake-global-rotation-tenant1.application1" + "http://application1.tenant1.global.vespa.yahooapis.com:4080/" ], + "rotationId": "rotation-id-1", "instances": [ { "environment": "prod", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json index fdd3dcc4d5c..b5ed2d407df 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json @@ -146,8 +146,9 @@ ], "compileVersion": "(ignore)", "globalRotations": [ - "http://fake-global-rotation-tenant1.application1" + "http://application1.tenant1.global.vespa.yahooapis.com:4080/" ], + "rotationId": "rotation-id-1", "instances": [ { "environment": "dev", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json index 41556c04209..caca0ad8970 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json @@ -146,8 +146,9 @@ ], "compileVersion": "6.1.0", "globalRotations": [ - "http://fake-global-rotation-tenant1.application1" + "http://application1.tenant1.global.vespa.yahooapis.com:4080/" ], + "rotationId": "rotation-id-1", "instances": [ @include(dev-us-west-1.json), @include(prod-corp-us-east-1.json) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json index d3927cbcfcf..79b9a785801 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json @@ -1,5 +1,5 @@ { - "user": "mytenant", + "user": "myuser", "tenants": @include(tenant-list.json), "tenantExists": false }
\ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json index 354bab4379c..8f8b76c83c6 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json @@ -19,6 +19,9 @@ "name": "DeploymentMetricsMaintainer" }, { + "name": "DnsMaintainer" + }, + { "name": "MetricsReporter" }, { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java index 55a4b46f4a7..d16a0222e4a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java @@ -6,7 +6,7 @@ import com.yahoo.application.container.handler.Request; import com.yahoo.component.Version; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; @@ -95,14 +95,12 @@ public class DeploymentApiTest extends ControllerContainerTest { private void deployCompletely(Application application, ApplicationPackage applicationPackage, long projectId, boolean success) { tester.notifyJobCompletion(application.id(), projectId, true, component); - tester.deploy(application, applicationPackage, new Zone(Environment.test, - RegionName.from("us-east-1")), projectId); + tester.deploy(application, applicationPackage, ZoneId.from(Environment.test, RegionName.from("us-east-1")), projectId); tester.notifyJobCompletion(application.id(), projectId, true, systemTest); - tester.deploy(application, applicationPackage, new Zone(Environment.staging, - RegionName.from("us-east-3")), projectId); + tester.deploy(application, applicationPackage, ZoneId.from(Environment.staging, RegionName.from("us-east-3")), projectId); tester.notifyJobCompletion(application.id(), projectId, success, stagingTest); if (success) { - tester.deploy(application, applicationPackage, new Zone(Environment.prod,RegionName.from("corp-us-east-1")), + tester.deploy(application, applicationPackage, ZoneId.from(Environment.prod, RegionName.from("corp-us-east-1")), projectId); tester.notifyJobCompletion(application.id(), projectId, true, productionCorpUsEast1); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java index e6b3eacd44e..1269bb23105 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java @@ -7,9 +7,8 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.config.SlimeUtils; -import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; @@ -40,8 +39,8 @@ import static org.junit.Assert.assertTrue; public class ScrewdriverApiTest extends ControllerContainerTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/responses/"; - private static final Zone testZone = new Zone(Environment.test, RegionName.from("us-east-1")); - private static final Zone stagingZone = new Zone(Environment.staging, RegionName.from("us-east-3")); + private static final ZoneId testZone = ZoneId.from(Environment.test, RegionName.from("us-east-1")); + private static final ZoneId stagingZone = ZoneId.from(Environment.staging, RegionName.from("us-east-3")); @Test public void testGetReleaseStatus() throws Exception { @@ -148,7 +147,7 @@ public class ScrewdriverApiTest extends ControllerContainerTest { tester.containerTester().updateSystemVersion(); Application app = tester.createApplication(); - tester.controller().applications().lockedOrThrow(app.id(), application -> + tester.controller().applications().lockOrThrow(app.id(), application -> tester.controller().applications().store(application.withProjectId(1))); // Unknown application diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java index a00665b77cb..2d92d10b661 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1; import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.vespa.hosted.controller.ZoneRegistryMock; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; @@ -21,12 +21,12 @@ import java.util.List; public class ZoneApiTest extends ControllerContainerTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/"; - private static final List<Zone> zones = Arrays.asList( - new Zone(Environment.prod, RegionName.from("us-north-1")), - new Zone(Environment.dev, RegionName.from("us-north-2")), - new Zone(Environment.test, RegionName.from("us-north-3")), - new Zone(Environment.staging, RegionName.from("us-north-4")) - ); + private static final List<ZoneId> zones = Arrays.asList( + ZoneId.from(Environment.prod, RegionName.from("us-north-1")), + ZoneId.from(Environment.dev, RegionName.from("us-north-2")), + ZoneId.from(Environment.test, RegionName.from("us-north-3")), + ZoneId.from(Environment.staging, RegionName.from("us-north-4")) + ); private ContainerControllerTester tester; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java index 63899d808f9..9c20c470cf8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java @@ -4,7 +4,7 @@ import com.yahoo.application.container.handler.Request; import com.yahoo.application.container.handler.Request.Method; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.ZoneId; import com.yahoo.text.Utf8; import com.yahoo.vespa.hosted.controller.ConfigServerProxyMock; import com.yahoo.vespa.hosted.controller.ZoneRegistryMock; @@ -26,12 +26,12 @@ import static org.junit.Assert.assertFalse; public class ZoneApiTest extends ControllerContainerTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/"; - private static final List<Zone> zones = Arrays.asList( - new Zone(Environment.prod, RegionName.from("us-north-1")), - new Zone(Environment.dev, RegionName.from("us-north-2")), - new Zone(Environment.test, RegionName.from("us-north-3")), - new Zone(Environment.staging, RegionName.from("us-north-4")) - ); + private static final List<ZoneId> zones = Arrays.asList( + ZoneId.from(Environment.prod, RegionName.from("us-north-1")), + ZoneId.from(Environment.dev, RegionName.from("us-north-2")), + ZoneId.from(Environment.test, RegionName.from("us-north-3")), + ZoneId.from(Environment.staging, RegionName.from("us-north-4")) + ); private ContainerControllerTester tester; private ConfigServerProxyMock proxy; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java new file mode 100644 index 00000000000..c259ae0ca60 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationTest.java @@ -0,0 +1,175 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.rotation; + +import com.yahoo.vespa.hosted.controller.Application; +import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.application.ApplicationRotation; +import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; +import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.net.URI; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Oyvind Gronnesby + * @author mpolden + */ +public class RotationTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final RotationsConfig rotationsConfig = new RotationsConfig( + new RotationsConfig.Builder() + .rotations("foo-1", "foo-1.com") + .rotations("foo-2", "foo-2.com") + ); + + private final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig( + new RotationsConfig.Builder() + .rotations("foo-1", "\n foo-1.com \n") + .rotations("foo-2", "foo-2.com") + ); + + private final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .region("us-west-1") + .build(); + + private DeploymentTester tester; + private RotationRepository repository; + private Application application; + + @Before + public void before() { + tester = new DeploymentTester(new ControllerTester(rotationsConfig)); + repository = tester.controller().applications().rotationRepository(); + application = tester.createApplication("app1", "tenant1", 11L,1L); + } + + @Test + public void assigns_and_reuses_rotation() { + // Deploying assigns a rotation + tester.deployCompletely(application, applicationPackage); + Rotation expected = new Rotation(new RotationId("foo-1"), "foo-1.com"); + + application = tester.applications().require(application.id()); + assertEquals(expected.id(), application.rotation().get().id()); + assertEquals(URI.create("http://app1.tenant1.global.vespa.yahooapis.com:4080/"), + application.rotation().get().url()); + try (RotationLock lock = repository.lock()) { + Rotation rotation = repository.getRotation(tester.applications().require(application.id()), lock); + assertEquals(expected, rotation); + } + + // Deploying once more assigns same rotation + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .region("us-west-1") + .searchDefinition("search foo { }") // Update application package so there is something to deploy + .build(); + tester.deployCompletely(application, applicationPackage); + assertEquals(expected.id(), tester.applications().require(application.id()).rotation().get().id()); + } + + @Test + public void strips_whitespace_in_rotation_fqdn() { + DeploymentTester tester = new DeploymentTester(new ControllerTester(rotationsConfigWhitespaces)); + RotationRepository repository = tester.controller().applications().rotationRepository(); + Application application = tester.createApplication("app2", "tenant2", 22L, + 2L); + tester.deployCompletely(application, applicationPackage); + application = tester.applications().require(application.id()); + + try (RotationLock lock = repository.lock()) { + Rotation rotation = repository.getRotation(application, lock); + Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com"); + assertEquals(assignedRotation, rotation); + } + } + + + @Test + public void out_of_rotations() { + // Assigns 1 rotation + tester.deployCompletely(application, applicationPackage); + + // Assigns 1 more + Application application2 = tester.createApplication("app2", "tenant2", 22L, + 2L); + tester.deployCompletely(application2, applicationPackage); + + // We're now out of rotations + thrown.expect(IllegalStateException.class); + thrown.expectMessage("no rotations available"); + Application application3 = tester.createApplication("app3", "tenant3", 33L, + 3L); + tester.deployCompletely(application3, applicationPackage); + } + + @Test + public void too_few_zones() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .build(); + Application application = tester.createApplication("app2", "tenant2", 22L, + 2L); + thrown.expect(RuntimeException.class); + thrown.expectMessage("less than 2 prod zones are defined"); + tester.deployCompletely(application, applicationPackage); + } + + @Test + public void no_rotation_assigned_for_application_without_service_id() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .region("us-east-3") + .region("us-west-1") + .build(); + tester.deployCompletely(application, applicationPackage); + Application app = tester.applications().require(application.id()); + Optional<ApplicationRotation> rotation = app.rotation(); + assertFalse(rotation.isPresent()); + } + + @Test + public void application_with_only_one_non_corp_region() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .region("corp-us-east-1") + .build(); + Application application = tester.createApplication("app2", "tenant2", 22L, + 2L); + thrown.expect(RuntimeException.class); + thrown.expectMessage("less than 2 prod zones are defined"); + tester.deployCompletely(application, applicationPackage); + } + + @Test + public void application_with_corp_region_and_two_non_corp_region() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .globalServiceId("foo") + .region("us-east-3") + .region("corp-us-east-1") + .region("us-west-1") + .build(); + Application application = tester.createApplication("app2", "tenant2", 22L, + 2L); + tester.deployCompletely(application, applicationPackage); + assertEquals(new RotationId("foo-1"), tester.applications().require(application.id()) + .rotation().get().id()); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java deleted file mode 100644 index 561799529f9..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/rotation/ControllerRotationRepositoryTest.java +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.rotation; - -import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.metrics.simple.MetricReceiver; -import com.yahoo.vespa.hosted.controller.api.identifiers.RotationId; -import com.yahoo.vespa.hosted.controller.api.rotation.Rotation; -import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; -import com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb; -import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import java.io.StringReader; -import java.net.URI; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author Oyvind Gronnesby - */ -public class ControllerRotationRepositoryTest { - - private final RotationsConfig rotationsConfig = new RotationsConfig( - new RotationsConfig.Builder() - .rotations("foo-1", "foo-1.com") - .rotations("foo-2", "foo-2.com") - ); - private final RotationsConfig rotationsConfigWhitespaces = new RotationsConfig( - new RotationsConfig.Builder() - .rotations("foo-1", "\n foo-1.com \n") - .rotations("foo-2", "foo-2.com") - ); - private final ControllerDb controllerDb = new MemoryControllerDb(); - private final ApplicationId applicationId = ApplicationId.from("msbe", "tumblr-search", "default"); - - @Rule public ExpectedException thrown = ExpectedException.none(); - - private final DeploymentSpec deploymentSpec = DeploymentSpec.fromXml( - new StringReader( - "<deployment>" + - " <prod global-service-id='foo'>" + - " <region active='true'>us-east</region>" + - " <region active='true'>us-west</region>" + - " </prod>" + - "</deployment>" - ) - ); - - private final DeploymentSpec deploymentSpecOneRegion = DeploymentSpec.fromXml( - new StringReader( - "<deployment>" + - " <prod global-service-id='nalle'>" + - " <region active='true'>us-east</region>" + - " </prod>" + - "</deployment>" - ) - ); - - private final DeploymentSpec deploymentSpecNoServiceId = DeploymentSpec.fromXml( - new StringReader( - "<deployment>" + - " <prod>" + - " <region active='true'>us-east</region>" + - " <region active='true'>us-west</region>" + - " </prod>" + - "</deployment>" - ) - ); - - private final DeploymentSpec deploymentSpecOnlyOneNonCorpRegion = DeploymentSpec.fromXml( - new StringReader( - "<deployment>" + - " <prod global-service-id='nalle'>" + - " <region active='true'>us-east</region>" + - " <region active='true'>corp-us-west</region>" + - " </prod>" + - "</deployment>" - ) - ); - - private final DeploymentSpec deploymentSpecWithAdditionalCorpZone = DeploymentSpec.fromXml( - new StringReader( - "<deployment>" + - " <prod global-service-id='nalle'>" + - " <region active='true'>us-east</region>" + - " <region active='true'>corp-us-west</region>" + - " <region active='true'>us-west</region>" + - " </prod>" + - "</deployment>" - ) - ); - - private ControllerRotationRepository repository; - private ControllerRotationRepository repositoryWhitespaces; - - - @Before - public void setup_repository() { - repository = new ControllerRotationRepository(rotationsConfig, controllerDb, MetricReceiver.nullImplementation); - repositoryWhitespaces = new ControllerRotationRepository(rotationsConfigWhitespaces, controllerDb, MetricReceiver.nullImplementation); - controllerDb.assignRotation(new RotationId("foo-1"), applicationId); - } - - @Test - public void application_with_rotation_reused() { - Set<Rotation> rotations = repository.getOrAssignRotation(applicationId, deploymentSpec); - Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com"); - assertContainsOnly(assignedRotation, rotations); - } - - @Test - public void names_stripped() { - Set<Rotation> rotations = repositoryWhitespaces.getOrAssignRotation(applicationId, deploymentSpec); - Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com"); - assertContainsOnly(assignedRotation, rotations); - } - - @Test - public void application_without_rotation() { - ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default"); - Set<Rotation> rotations = repository.getOrAssignRotation(other, deploymentSpec); - Rotation assignedRotation = new Rotation(new RotationId("foo-2"), "foo-2.com"); - assertContainsOnly(assignedRotation, rotations); - } - - @Test - public void application_without_rotation_but_none_left() { - application_without_rotation(); // run this test to assign last rotation - ApplicationId third = ApplicationId.from("thirdtenant", "thirdapplication", "default"); - - thrown.expect(RuntimeException.class); - thrown.expectMessage("no rotations available"); - - repository.getOrAssignRotation(third, deploymentSpec); - } - - @Test - public void application_without_rotation_but_does_not_qualify() { - ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default"); - - thrown.expect(RuntimeException.class); - thrown.expectMessage("less than 2 prod zones are defined"); - - repository.getOrAssignRotation(other, deploymentSpecOneRegion); - } - - @Test - public void application_with_rotation_but_does_not_qualify() { - Set<Rotation> rotations = repository.getOrAssignRotation(applicationId, deploymentSpecOneRegion); - Rotation assignedRotation = new Rotation(new RotationId("foo-1"), "foo-1.com"); - assertContainsOnly(assignedRotation, rotations); - } - - @Test - public void application_with_rotation_is_listed() { - repository.getOrAssignRotation(applicationId, deploymentSpec); - Set<URI> uris = repository.getRotationUris(applicationId); - assertEquals(Collections.singleton(URI.create("http://tumblr-search.msbe.global.vespa.yahooapis.com:4080/")), uris); - } - - @Test - public void application_without_rotation_is_empty() { - ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default"); - Set<URI> uris = repository.getRotationUris(other); - assertTrue(uris.isEmpty()); - } - - @Test - public void application_without_serviceid_and_two_regions() { - ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default"); - Set<Rotation> rotations = repository.getOrAssignRotation(other, deploymentSpecNoServiceId); - assertTrue(rotations.isEmpty()); - } - - @Test - public void application_with_only_one_non_corp_region() { - ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default"); - - thrown.expect(RuntimeException.class); - thrown.expectMessage("less than 2 prod zones are defined"); - - repository.getOrAssignRotation(other, deploymentSpecOnlyOneNonCorpRegion); - } - - @Test - public void application_with_corp_region_and_two_non_corp_region() { - ApplicationId other = ApplicationId.from("othertenant", "otherapplication", "default"); - Set<Rotation> rotations = repository.getOrAssignRotation(other, deploymentSpecWithAdditionalCorpZone); - assertContainsOnly(new Rotation(new RotationId("foo-2"), "foo-2.com"), rotations); - } - - private static <T> void assertContainsOnly(T item, Collection<T> items) { - assertTrue("Collection contains only " + item.toString(), - items.size() == 1 && items.contains(item)); - } - -} diff --git a/defaults/pom.xml b/defaults/pom.xml index 2f3f06780e7..4d1a08aacc2 100644 --- a/defaults/pom.xml +++ b/defaults/pom.xml @@ -5,9 +5,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.yahoo.vespa</groupId> - <artifactId>parent</artifactId> - <version>6-SNAPSHOT</version> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa</groupId> <artifactId>defaults</artifactId> diff --git a/dist/build-rpm.sh b/dist/build-rpm.sh new file mode 100755 index 00000000000..e86eebe9380 --- /dev/null +++ b/dist/build-rpm.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +set -e + +Usage() { + cat <<EOF +Usage: ${0##*/} [OPTIONS]...SPECFILE +Run rpmbuild with the given specfile macros, creating TOPDIR if necessary. + +Options: + -b BUILDDIR Overrides %_builddir. + -d DIST The %dist to build for (e.g. .el7 for RHEL 7). Can be specified + multiple time to build multiple RPMs. The default %dist is used + if no -d options have been specified. + -h Print this help text and exit. + -t TOPDIR Overrides %_topdir. + -v VERSION [Required] The version of the RPM. +EOF + + exit 1 +} + +Fail() { + printf "%s\n" "$*" + exit 1 +} + +Run() { + local command="$1" + shift + printf "%q" "$command" + + local arg + for arg in "$@"; do + printf " %q" "$arg" + done + printf "\n" + + "$command" "$@" +} + +Main() { + local -a dists=() + local version= topdir= builddir= + while (( $# > 0 )); do + case "$1" in + -b|--builddir) + builddir="$2" + shift 2 + if ! test -d "$builddir"; then + Fail "BUILDDIR '$builddir' does not exist" + # Make builddir an absolute path + elif ! builddir=$(readlink -e "$builddir"); then + Fail "Failed to resolve BUILDDIR '$builddir'" + fi + ;; + -d|--dist) + local dist="$2" + shift 2 + case "$dist" in + .el6|.el7) : ;; + *) Fail "Bad DIST value '$dist'" ;; + esac + dists+=("$dist") + ;; + -t|--topdir) + topdir="$2" + shift 2 + if ! [[ "$topdir" =~ ^/ ]]; then + Fail "TOPDIR must be an absolute path" + fi + ;; + -v|--version) + version="$2" + shift 2 + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + Fail "VERSION must be a version of the form X.Y.Z" + fi + ;; + *) break ;; + esac + done + + if (( $# == 0 )); then + Fail "Missing SPECFILE" + elif (( $# > 1 )); then + Fail "Too many arguments" + else + case "$1" in + help|-h|--help) Usage ;; + esac + fi + local specfile="$1" + + local -a defines=() + + if test -n "$builddir"; then + defines+=(--define "_builddir $builddir") + fi + + if test -n "$topdir"; then + if ! mkdir -p "$topdir"; then + Fail "Failed to create TOPDIR directory '$topdir'" + fi + defines+=(--define "_topdir $topdir") + fi + + if test -n "$version"; then + defines+=(--define "version $version") + else + Fail "VERSION is required" + fi + + if (( ${#dists[@]} == 0 )); then + Run rpmbuild -bb "${defines[@]}" "$specfile" + else + local dist + for dist in "${dists[@]}"; do + Run rpmbuild -bb "${defines[@]}" --define "dist $dist" "$specfile" + done + fi +} + +Main "$@" diff --git a/dist/vespa.spec b/dist/vespa.spec index ca6a9504401..17a9aeb03f4 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -18,11 +18,11 @@ Source0: vespa-%{version}.tar.gz %if 0%{?centos} BuildRequires: epel-release BuildRequires: centos-release-scl -BuildRequires: devtoolset-6-gcc-c++ -BuildRequires: devtoolset-6-libatomic-devel -BuildRequires: devtoolset-6-binutils +BuildRequires: devtoolset-7-gcc-c++ +BuildRequires: devtoolset-7-libatomic-devel +BuildRequires: devtoolset-7-binutils BuildRequires: rh-maven33 -%define _devtoolset_enable /opt/rh/devtoolset-6/enable +%define _devtoolset_enable /opt/rh/devtoolset-7/enable %define _rhmaven33_enable /opt/rh/rh-maven33/enable %endif %if 0%{?fedora} @@ -68,6 +68,8 @@ BuildRequires: make BuildRequires: vespa-cppunit-devel >= 1.12.1-6 BuildRequires: vespa-libtorrent-devel >= 1.0.11-6 BuildRequires: systemd +BuildRequires: flex >= 2.5.0 +BuildRequires: bison >= 3.0.0 %if 0%{?centos} Requires: epel-release %endif @@ -195,13 +197,18 @@ exit 0 %postun %systemd_postun_with_restart vespa.service %systemd_postun_with_restart vespa-configserver.service -rm -f /etc/profile.d/vespa.sh -userdel vespa +if [ $1 -eq 0 ]; then # this is an uninstallation + rm -f /etc/profile.d/vespa.sh + userdel vespa +fi %files %defattr(-,vespa,vespa,-) %doc %{_prefix}/* +%config(noreplace) %{_prefix}/conf/logd/logd.cfg +%config(noreplace) %{_prefix}/conf/vespa/default-env.txt +%config(noreplace) %{_prefix}/etc/vespamalloc.conf %attr(644,root,root) /usr/lib/systemd/system/vespa.service %attr(644,root,root) /usr/lib/systemd/system/vespa-configserver.service diff --git a/docker-api/pom.xml b/docker-api/pom.xml index fc374a12dd2..449a94fb621 100644 --- a/docker-api/pom.xml +++ b/docker-api/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>docker-api</artifactId> diff --git a/docproc/pom.xml b/docproc/pom.xml index 09635201e51..1043de32128 100644 --- a/docproc/pom.xml +++ b/docproc/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>docproc</artifactId> <packaging>jar</packaging> diff --git a/docprocs/pom.xml b/docprocs/pom.xml index 1b209ec39ee..16259a861c7 100644 --- a/docprocs/pom.xml +++ b/docprocs/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>docprocs</artifactId> <packaging>container-plugin</packaging> diff --git a/document/pom.xml b/document/pom.xml index 81b21487314..10f71218422 100644 --- a/document/pom.xml +++ b/document/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>document</artifactId> <version>6-SNAPSHOT</version> diff --git a/document/src/main/java/com/yahoo/document/idstring/IdIdString.java b/document/src/main/java/com/yahoo/document/idstring/IdIdString.java index 7fcb530b347..44ff08c73f0 100644 --- a/document/src/main/java/com/yahoo/document/idstring/IdIdString.java +++ b/document/src/main/java/com/yahoo/document/idstring/IdIdString.java @@ -62,6 +62,9 @@ public class IdIdString extends IdString { if (hasSetLocation) { throw new IllegalArgumentException("Illegal key combination in " + keyValues); } + if (value.isEmpty()) { + throw new IllegalArgumentException("ID location value for 'n=' key is empty"); + } location = Long.parseLong(value); hasSetLocation = true; hasNumber = true; @@ -70,6 +73,9 @@ public class IdIdString extends IdString { if (hasSetLocation) { throw new IllegalArgumentException("Illegal key combination in " + keyValues); } + if (value.isEmpty()) { + throw new IllegalArgumentException("ID location value for 'g=' key is empty"); + } location = makeLocation(value); hasSetLocation = true; hasGroup = true; diff --git a/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java b/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java index 79a10bc72e4..bd769889363 100644 --- a/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java +++ b/document/src/test/java/com/yahoo/document/DocumentIdTestCase.java @@ -1,9 +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.document; -import com.yahoo.document.*; import com.yahoo.document.idstring.*; import com.yahoo.vespa.objects.BufferSerializer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; import java.math.BigInteger; @@ -12,16 +15,20 @@ import java.util.regex.Pattern; import java.util.Arrays; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -public class DocumentIdTestCase extends junit.framework.TestCase { +public class DocumentIdTestCase { DocumentTypeManager manager = new DocumentTypeManager(); - public DocumentIdTestCase(String name) { - super(name); - } + @Rule + public ExpectedException expectedException = ExpectedException.none(); - protected void setUp() { + @Before + public void setUp() { DocumentType testDocType = new DocumentType("testdoc"); testDocType.addHeaderField("intattr", DataType.INT); @@ -33,6 +40,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { manager.registerDocumentType(testDocType); } + @Test public void testCompareTo() { DocumentId docId1 = new Document(manager.getDocumentType("testdoc"), new DocumentId("doc:testdoc:http://www.uio.no/")).getId(); DocumentId docId2 = new Document(manager.getDocumentType("testdoc"), new DocumentId("doc:testdoc:http://www.uio.no/")).getId(); @@ -56,6 +64,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { } } + @Test public void testValidInvalidUriSchemes() { try { //valid URIs @@ -92,9 +101,23 @@ public class DocumentIdTestCase extends junit.framework.TestCase { checkInvalidUri("id:namespace:type:n=0,g=foo:foo"); } + @Test + public void empty_user_location_value_throws_exception() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("ID location value for 'n=' key is empty"); + new DocumentId("id:namespace:type:n=:foo"); + } + + @Test + public void empty_group_location_value_throws_exception() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("ID location value for 'g=' key is empty"); + new DocumentId("id:namespace:type:g=:foo"); + } //Compares globalId with C++ implementation located in // ~document-HEAD/document/src/tests/cpp-globalidbucketids.txt + @Test public void testCalculateGlobalId() throws IOException{ String file = "src/tests/cpp-globalidbucketids.txt"; @@ -135,6 +158,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { //Compares bucketId with C++ implementation located in // ~document-HEAD/document/src/tests/cpp-globalidbucketids.txt + @Test public void testGetBucketId() throws IOException{ String file = "src/tests/cpp-globalidbucketids.txt"; BufferedReader fr = new BufferedReader(new FileReader(file)); @@ -153,6 +177,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { fr.close(); } + @Test public void testGroupdoc() { try { //valid @@ -166,11 +191,13 @@ public class DocumentIdTestCase extends junit.framework.TestCase { } } + @Test public void testInvalidGroupdoc() { checkInvalidUri("grouppdoc:blabla:something"); checkInvalidUri("groupdoc:blablasomething"); } + @Test public void testUriNamespace() { DocumentId docId = new DocumentId("doc:bar:foo"); assertEquals("doc:bar:foo", docId.toString()); @@ -217,6 +244,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { assertEquals(1268182861, ((OrderDocIdString)docId.getScheme()).getOrdering()); } + @Test public void testIdStrings() { DocumentId docId; docId = new DocumentId(new DocIdString("test", "baaaa")); @@ -240,6 +268,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { assertEquals("type", docId.getDocType()); } + @Test public void testIdStringFeatures() { DocumentId none = new DocumentId("id:ns:type::foo"); assertFalse(none.getScheme().hasGroup()); @@ -276,6 +305,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { assertEquals(42, order.getScheme().getNumber()); } + @Test public void testHashCodeOfGids() { DocumentId docId0 = new DocumentId("doc:blabla:0"); byte[] docId0Gid = docId0.getGlobalId(); @@ -295,6 +325,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { assertEquals(Arrays.hashCode(docId0Gid), Arrays.hashCode(docId0CopyGid)); } + @Test public void testDocumentIdCanOnlyContainTextCharacters() throws UnsupportedEncodingException { assertExceptionWhenConstructing(new byte[]{105, 100, 58, 97, 58, 98, 58, 58, 0, 99}, // "id:a:b::0x0c" "illegal code point 0x0"); @@ -313,6 +344,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { } } + @Test public void testSerializedDocumentIdCanContainNonTextCharacter() throws UnsupportedEncodingException { String strId = new String(new byte[]{105, 100, 58, 97, 58, 98, 58, 58, 7, 99}); // "id:a:b::0x7c" DocumentId docId = DocumentId.createFromSerialized(strId); @@ -328,6 +360,7 @@ public class DocumentIdTestCase extends junit.framework.TestCase { } } + @Test public void testSerializedDocumentIdCannotContainZeroByte() throws UnsupportedEncodingException { String strId = new String(new byte[]{105, 100, 58, 97, 58, 98, 58, 58, 0, 99}); // "id:a:b::0x0c" try { diff --git a/document/src/tests/bucketselectortest.cpp b/document/src/tests/bucketselectortest.cpp index 0f8520745f1..e0857a32dba 100644 --- a/document/src/tests/bucketselectortest.cpp +++ b/document/src/tests/bucketselectortest.cpp @@ -85,8 +85,6 @@ void BucketSelectorTest::testSimple() ASSERT_BUCKET_COUNT("id.bucket == 0x4000000000000258", 1u); // Bucket 600 ASSERT_BUCKET_COUNT("(testdoctype1 and id.bucket=0)", 1u); - ASSERT_BUCKET_COUNT("searchcolumn.3 = 1", 21845u); - // Check that the correct buckets is found ASSERT_BUCKET("id = \"userdoc:ns:123:foobar\"", document::BucketId(58, 123)); diff --git a/document/src/tests/documentselectparsertest.cpp b/document/src/tests/documentselectparsertest.cpp index c5715ae5114..db7b48cdc3a 100644 --- a/document/src/tests/documentselectparsertest.cpp +++ b/document/src/tests/documentselectparsertest.cpp @@ -1,6 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - #include <cppunit/TestFixture.h> #include <cppunit/extensions/HelperMacros.h> #include <vespa/document/repo/configbuilder.h> @@ -12,12 +11,16 @@ #include <vespa/document/select/visitor.h> #include <vespa/document/select/bodyfielddetector.h> #include <vespa/document/select/valuenode.h> +#include <vespa/document/select/valuenodes.h> #include <vespa/document/select/branch.h> #include <vespa/document/select/simpleparser.h> #include <vespa/document/select/constant.h> #include <vespa/document/select/invalidconstant.h> #include <vespa/document/select/doctype.h> #include <vespa/document/select/compare.h> +#include <vespa/document/select/parse_utils.h> +#include <vespa/vespalib/util/exceptions.h> +#include <limits> using namespace document::config_builder; @@ -34,6 +37,15 @@ class DocumentSelectParserTest : public CppUnit::TestFixture { CPPUNIT_TEST(testThatComplexFieldValuesHaveCorrectFieldNames); CPPUNIT_TEST(testBodyFieldDetection); CPPUNIT_TEST(testDocumentUpdates); + CPPUNIT_TEST(test_syntax_error_reporting); + CPPUNIT_TEST(test_operator_precedence); + CPPUNIT_TEST(test_token_used_as_ident_preserves_casing); + CPPUNIT_TEST(test_ambiguous_field_spec_expression_is_handled_correctly); + CPPUNIT_TEST(test_can_build_field_value_from_field_expr_node); + CPPUNIT_TEST(test_can_build_function_call_from_field_expr_node); + CPPUNIT_TEST(test_function_call_on_doctype_throws_exception); + CPPUNIT_TEST(test_parse_utilities_handle_well_formed_input); + CPPUNIT_TEST(test_parse_utilities_handle_malformed_input); CPPUNIT_TEST_SUITE_END(); BucketIdFactory _bucketIdFactory; @@ -51,11 +63,13 @@ class DocumentSelectParserTest : public CppUnit::TestFixture { const std::string& hstr); std::unique_ptr<select::FieldValueNode> - parseFieldValue(const std::string expression); + parseFieldValue(const std::string& expression); template <typename ContainsType> select::ResultList doParse(const vespalib::stringref& expr, const ContainsType& t); + + std::string parse_to_tree(const std::string& str); public: DocumentSelectParserTest() @@ -88,7 +102,15 @@ public: void testDocumentUpdates2(); void testDocumentUpdates3(); void testDocumentUpdates4(); - void testDocumentUpdates5(); + void test_syntax_error_reporting(); + void test_operator_precedence(); + void test_token_used_as_ident_preserves_casing(); + void test_ambiguous_field_spec_expression_is_handled_correctly(); + void test_can_build_field_value_from_field_expr_node(); + void test_can_build_function_call_from_field_expr_node(); + void test_function_call_on_doctype_throws_exception(); + void test_parse_utilities_handle_well_formed_input(); + void test_parse_utilities_handle_malformed_input(); }; CPPUNIT_TEST_SUITE_REGISTRATION(DocumentSelectParserTest); @@ -111,9 +133,9 @@ void DocumentSelectParserTest::setUp() builder.document(-1673092522, "usergroup", Struct("usergroup.header"), Struct("usergroup.body")); - _repo.reset(new DocumentTypeRepo(builder.config())); + _repo = std::make_unique<DocumentTypeRepo>(builder.config()); - _parser.reset(new select::Parser(*_repo, _bucketIdFactory)); + _parser = std::make_unique<select::Parser>(*_repo, _bucketIdFactory); } Document::SP DocumentSelectParserTest::createDoc( @@ -319,11 +341,45 @@ void verifyParse(const std::string& query, const char* expected = 0) { } } +void DocumentSelectParserTest::test_syntax_error_reporting() { + createDocs(); + + verifyFailedParse("testdoctype1.headerval == aaa", "ParsingFailedException: " + "syntax error, unexpected end of input, expecting . at column 30 " + "when parsing selection 'testdoctype1.headerval == aaa'"); + // TODO improve error reporting of broken escape sequences. Current error messages + // are not too helpful since we simply fail to parse the string token altogether. + verifyFailedParse("testdoctype1.headerval == \"tab\\x0notcomplete\"", + "ParsingFailedException: Unexpected character: '\\\"' at column 27 " + "when parsing selection 'testdoctype1.headerval == \"tab\\x0notcomplete\"'"); + verifyFailedParse("testdoctype1.headerval == \"tab\\ysf\"", + "ParsingFailedException: Unexpected character: '\\\"' at column 27 " + "when parsing selection 'testdoctype1.headerval == \"tab\\ysf\"'"); + // Test illegal operator + verifyFailedParse("testdoctype1.headerval <> 12", "ParsingFailedException: syntax error, " + "unexpected > at column 25 when parsing selection 'testdoctype1.headerval <> 12'"); + + // This will trigger a missing doctype error instead of syntax error, as "fal" + // will be reduced into a doctype rule. + verifyFailedParse("fal se", "ParsingFailedException: Document type 'fal' " + "not found at column 1 when parsing selection 'fal se'"); + + verifyFailedParse("mytype", "ParsingFailedException: Document type 'mytype' not found"); + + verifyFailedParse("mytype.foo.bar", "ParsingFailedException: Document type 'mytype' not found"); + + verifyFailedParse("testdoctype1 == 8", "ParsingFailedException: syntax error, unexpected ==, " + "expecting end of input at column 14 when parsing selection 'testdoctype1 == 8'"); + + verifyFailedParse("(1 + 2)", "ParsingFailedException: expected field spec, " + "doctype, bool or comparison at column 1 when parsing selection '(1 + 2)'"); +} + void DocumentSelectParserTest::testParseTerminals() { createDocs(); - // Test number value + // Test number value verifyParse("", "true"); verifyParse("testdoctype1.headerval == 123"); verifyParse("testdoctype1.headerval == +123.53", "testdoctype1.headerval == 123.53"); @@ -332,10 +388,8 @@ void DocumentSelectParserTest::testParseTerminals() "testdoctype1.headerval == 2.34124e+08"); verifyParse("testdoctype1.headerval == -234123.523E-3", "testdoctype1.headerval == -234.124"); - verifyFailedParse("testdoctype1.headerval == aaa", "ParsingFailedException: " - "Unexpected token at position 23 ('== aaa') in query " - "'testdoctype1.headerval == aaa', at fullParse in "); - // Test string value + + // Test string value verifyParse("testdoctype1.headerval == \"test\""); std::unique_ptr<select::Node> node( _parser->parse("testdoctype1.headerval == \"test\"")); @@ -345,64 +399,46 @@ void DocumentSelectParserTest::testParseTerminals() dynamic_cast<const select::FieldValueNode&>(compnode.getLeft())); const select::StringValueNode& vnode( dynamic_cast<const select::StringValueNode&>(compnode.getRight())); - /* - CPPUNIT_ASSERT_EQUAL(vespalib::string("testdoctype1"), - fnode.getDocType()->getName()); - */ + CPPUNIT_ASSERT_EQUAL(vespalib::string("headerval"), fnode.getFieldName()); CPPUNIT_ASSERT_EQUAL(vespalib::string("test"), vnode.getValue()); - // Test whitespace + // Test whitespace verifyParse("testdoctype1.headerval == \"te st \""); verifyParse(" \t testdoctype1.headerval\t== \t \"test\"\t", "testdoctype1.headerval == \"test\""); - // Test escaping + // Test escaping verifyParse("testdoctype1.headerval == \"tab\\ttest\""); verifyParse("testdoctype1.headerval == \"tab\\x09test\"", "testdoctype1.headerval == \"tab\\ttest\""); verifyParse("testdoctype1.headerval == \"tab\\x055test\""); - verifyFailedParse("testdoctype1.headerval == \"tab\\x0notcomplete\"", - "ParsingFailedException: Unexpected token at position 23 " - "('== \"tab\\x0') in query 'testdoctype1.headerval == \"tab\\x0notcomplete\"', " - "at fullParse in "); - verifyFailedParse("testdoctype1.headerval == \"tab\\ysf\"", - "ParsingFailedException: Unexpected token at position 23 " - "('== \"tab\\ys') in query 'testdoctype1.headerval == \"tab\\ysf\"', " - "at fullParse in "); node = _parser->parse("testdoctype1.headerval == \"\\tt\\x48 \\n\""); select::Compare& escapednode(dynamic_cast<select::Compare&>(*node)); const select::StringValueNode& escval( dynamic_cast<const select::StringValueNode&>(escapednode.getRight())); CPPUNIT_ASSERT_EQUAL(vespalib::string("\ttH \n"), escval.getValue()); - // Test illegal operator - verifyFailedParse("testdoctype1.headerval <> 12", "ParsingFailedException: Unexpected" - " token at position 23 ('<> 12') in query 'testdoctype1.headerval <> 12', at"); - // Test <= <, > >= + // Test <= <, > >= verifyParse("testdoctype1.headerval >= 123"); verifyParse("testdoctype1.headerval > 123"); verifyParse("testdoctype1.headerval <= 123"); verifyParse("testdoctype1.headerval < 123"); verifyParse("testdoctype1.headerval != 123"); - // Test defined + // Test defined verifyParse("testdoctype1.headerval", "testdoctype1.headerval != null"); - // Test bools - verifyParse("TRUE"); - verifyParse("FALSE"); + // Test bools + verifyParse("TRUE", "true"); + verifyParse("FALSE", "false"); verifyParse("true"); verifyParse("false"); - verifyParse("faLSe"); - verifyFailedParse("fal se", "ParsingFailedException: Unexpected token at " - "position 4 ('se') in query 'fal se', at"); + verifyParse("faLSe", "false"); - // Test document types + // Test document types verifyParse("testdoctype1"); - verifyFailedParse("mytype", "ParsingFailedException: Document type mytype " - "not found"); verifyParse("_test_doctype3_"); verifyParse("_test_doctype3_._only_in_child_ == 0"); - // Test document id with simple parser. + // Test document id with simple parser. verifySimpleParse("id == \"userdoc:ns:mytest\""); verifySimpleParse("id.namespace == \"myspace\""); verifySimpleParse("id.scheme == \"userdoc\""); @@ -411,7 +447,7 @@ void DocumentSelectParserTest::testParseTerminals() verifySimpleParse("id.user == 1234"); verifySimpleParse("id.user == 0x12456ab", "id.user == 19158699"); - // Test document id + // Test document id verifyParse("id == \"userdoc:ns:mytest\""); verifyParse("id.namespace == \"myspace\""); verifyParse("id.scheme == \"userdoc\""); @@ -429,28 +465,23 @@ void DocumentSelectParserTest::testParseTerminals() "id.bucket == -9223372036854775566"); verifyParse("id.gid == \"gid(0xd755743aea262650274d70f0)\""); - // Test search column - verifyParse("searchcolumn.10 == 2"); - - // Test other operators + // Test other operators verifyParse("id.scheme = \"*doc\""); verifyParse("testdoctype1.hstringval =~ \"(john|barry|shrek)\""); - // Verify functions + // Verify functions verifyParse("id.hash() == 124"); verifyParse("id.specific.hash() == 124"); verifyParse("testdoctype1.hstringval.lowercase() == \"chang\""); verifyParse("testdoctype1.hstringval.lowercase().hash() == 124"); - verifyFailedParse("testdoctype1 == 8", "ParsingFailedException: Unexpected token" - " at position 13 ('== 8') in query 'testdoctype1 == 8', at fullParse in "); verifyParse("testdoctype1.hintval > now()"); verifyParse("testdoctype1.hintval > now().abs()"); - // Value grouping + // Value grouping verifyParse("(123) < (200)"); verifyParse("(\"hmm\") < (id.scheme)"); - // Arithmetics + // Arithmetics verifyParse("1 + 2 > 1"); verifyParse("1 - 2 > 1"); verifyParse("1 * 2 > 1"); @@ -459,11 +490,11 @@ void DocumentSelectParserTest::testParseTerminals() verifyParse("(1 + 2) * (4 - 2) == 1"); verifyParse("23 + 643 / 34 % 10 > 34"); - // CJK stuff + // CJK stuff verifyParse("testdoctype1.hstringval = \"\xE4\xB8\xBA\xE4\xBB\x80\"", "testdoctype1.hstringval = \"\\xe4\\xb8\\xba\\xe4\\xbb\\x80\""); - // Strange doctype names + // Strange doctype names verifyParse("notandor"); verifyParse("ornotand"); verifyParse("andornot"); @@ -475,16 +506,16 @@ void DocumentSelectParserTest::testParseBranches() { createDocs(); - verifyParse("TRUE or FALSE aNd FALSE oR TRUE"); - verifyParse("TRUE and FALSE or FALSE and TRUE"); - verifyParse("TRUE or FALSE and FALSE or TRUE"); - verifyParse("(TRUE or FALSE) and (FALSE or TRUE)"); + verifyParse("TRUE or FALSE aNd FALSE oR TRUE", "true or false and false or true"); + verifyParse("TRUE and FALSE or FALSE and TRUE", "true and false or false and true"); + verifyParse("TRUE or FALSE and FALSE or TRUE", "true or false and false or true"); + verifyParse("(TRUE or FALSE) and (FALSE or TRUE)", "(true or false) and (false or true)"); verifyParse("true or (not false) and not true"); - // Test number branching with node branches + // Test number branching with node branches verifyParse("((243) < 300 and (\"FOO\").lowercase() == (\"foo\"))"); - // Strange doctype names + // Strange doctype names verifyParse("notandor and ornotand"); verifyParse("ornotand or andornot"); verifyParse("not andornot"); @@ -554,7 +585,7 @@ void DocumentSelectParserTest::testOperators0() std::cerr << ost.str() << "\n"; } // */ - // Check that comparison operators work. + // Check that comparison operators work. PARSE("", *_doc[0], True); PARSE("30 < 10", *_doc[0], False); PARSE("10 < 30", *_doc[0], True); @@ -593,7 +624,7 @@ void DocumentSelectParserTest::testOperators1() { createDocs(); - // Mix of types should within numbers, but otherwise not match + // Mix of types should within numbers, but otherwise not match PARSE("30 < 10.2", *_doc[0], False); PARSE("10.2 < 30", *_doc[0], True); PARSE("30 < \"foo\"", *_doc[0], Invalid); @@ -606,7 +637,7 @@ void DocumentSelectParserTest::testOperators1() PARSE("14.3 == null", *_doc[0], False); PARSE("null = 0", *_doc[0], False); - // Field values + // Field values PARSE("testdoctype1.headerval = 24", *_doc[0], True); PARSE("testdoctype1.headerval = 24", *_doc[1], False); PARSE("testdoctype1.headerval = 13", *_doc[0], False); @@ -625,11 +656,11 @@ void DocumentSelectParserTest::testOperators1() PARSE("testdoctype1.byteweightedset == 7", *_doc[1], False); PARSE("testdoctype1.byteweightedset == 5", *_doc[1], True); - // Document types + // Document types PARSE("testdoctype1", *_doc[0], True); PARSE("testdoctype2", *_doc[0], False); - // Inherited doctypes + // Inherited doctypes PARSE("testdoctype2", *_doc[4], True); PARSE("testdoctype2", *_doc[3], False); PARSE("testdoctype1", *_doc[4], True); @@ -640,7 +671,7 @@ void DocumentSelectParserTest::testOperators2() { createDocs(); - // Id values + // Id values PARSEI("id == \"doc:myspace:anything\"", *_doc[0], True); PARSEI(" iD== \"doc:myspace:anything\" ", *_doc[0], True); PARSEI("id == \"doc:myspa:nything\"", *_doc[0], False); @@ -702,16 +733,13 @@ void DocumentSelectParserTest::testOperators3() PARSEI("id.user = 1234", *_doc[8], True); PARSEI("id.group == \"1234\"", *_doc[8], True); PARSEI("id.group == \"mygroup\"", *_doc[9], True); - - // Searchcolumn policy - PARSE("searchcolumn.10 == 8", *_doc[0], True); } void DocumentSelectParserTest::testOperators4() { createDocs(); - // Branch operators + // Branch operators PARSEI("true and false", *_doc[0], False); PARSEI("true and true", *_doc[0], True); PARSEI("true or false", *_doc[0], True); @@ -723,7 +751,7 @@ void DocumentSelectParserTest::testOperators4() PARSEI("true and not false or false", *_doc[0], True); PARSEI("((243 < 300) and (\"FOO\".lowercase() == \"foo\"))", *_doc[0], True); - // Invalid branching. testdoctype1.content = 1 is invalid + // Invalid branching. testdoctype1.content = 1 is invalid PARSE("testdoctype1.content = 1 and true", *_doc[0], Invalid); PARSE("testdoctype1.content = 1 or true", *_doc[0], True); PARSE("testdoctype1.content = 1 and false", *_doc[0], False); @@ -738,7 +766,7 @@ void DocumentSelectParserTest::testOperators5() { createDocs(); - // Functions + // Functions PARSE("testdoctype1.hstringval.lowercase() == \"Yet\"", *_doc[3], False); PARSE("testdoctype1.hstringval.lowercase() == \"yet\"", *_doc[3], True); PARSE("testdoctype1.hfloatval.lowercase() == \"yet\"", *_doc[3], Invalid); @@ -754,7 +782,7 @@ void DocumentSelectParserTest::testOperators5() PARSE("now() < 1311862500", *_doc[10], False); PARSE("now() > 1611862500", *_doc[10], False); - // Arithmetics + // Arithmetics PARSEI("id.specific.hash() % 10 = 8", *_doc[0], True); PARSEI("id.specific.hash() % 10 = 2", *_doc[0], False); PARSEI("\"foo\" + \"bar\" = \"foobar\"", *_doc[0], True); @@ -767,14 +795,18 @@ void DocumentSelectParserTest::testOperators6() { createDocs(); - // CJK - // Assuming the characters " \ ? * is not used as part of CJK tokens + // CJK + // Assuming the characters " \ ? * is not used as part of CJK tokens PARSE("testdoctype1.content=\"\xE4\xB8\xBA\xE4\xBB\x80\"", *_doc[3], True); PARSE("testdoctype1.content=\"\xE4\xB7\xBA\xE4\xBB\x80\"", *_doc[3], False); - // Structs and arrays + // Structs and arrays PARSE("testdoctype1.mystruct", *_doc[0], False); PARSE("testdoctype1.mystruct", *_doc[1], True); + PARSE("(testdoctype1.mystruct)", *_doc[0], False); + PARSE("(testdoctype1.mystruct)", *_doc[1], True); + PARSE("(((testdoctype1.mystruct)))", *_doc[0], False); + PARSE("(((testdoctype1.mystruct)))", *_doc[1], True); PARSE("testdoctype1.mystruct", *_doc[2], False); PARSE("testdoctype1.mystruct == testdoctype1.mystruct", *_doc[0], True); PARSE("testdoctype1.mystruct == testdoctype1.mystruct", *_doc[1], True); @@ -812,6 +844,7 @@ void DocumentSelectParserTest::testOperators7() PARSE("testdoctype1.structarray", *_doc[1], True); PARSE("testdoctype1.structarray.key == 15", *_doc[1], True); PARSE("testdoctype1.structarray[1].key == 16", *_doc[1], True); + PARSE("testdoctype1.structarray[1].key", *_doc[1], True); // "key is set?" expr PARSE("testdoctype1.structarray[1].key = 16", *_doc[1], True); PARSE("testdoctype1.structarray.value == \"structval1\"", *_doc[0], False); PARSE("testdoctype1.structarray[4].value == \"structval1\"", *_doc[0], False); @@ -952,7 +985,6 @@ namespace { void visitArithmeticValueNode(const select::ArithmeticValueNode &) override {} void visitFunctionValueNode(const select::FunctionValueNode &) override {} void visitIdValueNode(const select::IdValueNode &) override {} - void visitSearchColumnValueNode(const select::SearchColumnValueNode &) override {} void visitFieldValueNode(const select::FieldValueNode &) override {} void visitFloatValueNode(const select::FloatValueNode &) override {} void visitVariableValueNode(const select::VariableValueNode &) override {} @@ -977,15 +1009,14 @@ void DocumentSelectParserTest::testVisitor() TestVisitor v; root->visit(v); + std::string expected = - "OR(CONSTANT(true), " - "AND(DOCTYPE(testdoctype1), " - "AND(OR(NOT(COMPARE(id.user = 12)), " - "COMPARE(testdoctype1.hstringval = \"ola\")), " - "COMPARE(testdoctype1.headerval != null)" - ")" - ")" - ")"; + "OR(CONSTANT(true), " + "AND(AND(DOCTYPE(testdoctype1), " + "OR(NOT(COMPARE(id.user = 12)), " + "COMPARE(testdoctype1.hstringval = \"ola\"))), " + "COMPARE(testdoctype1.headerval != null)))"; + CPPUNIT_ASSERT_EQUAL(expected, v.getVisitString()); } @@ -1093,13 +1124,15 @@ void DocumentSelectParserTest::testDocumentUpdates0() PARSEI("\"\" =~ \"\"", *_update[0], True); PARSEI("30 = 10", *_update[0], False); PARSEI("30 = 30", *_update[0], True); + PARSEI("(30 = 10)", *_update[0], False); + PARSEI("(30 = 30)", *_update[0], True); } void DocumentSelectParserTest::testDocumentUpdates1() { createDocs(); - // Mix of types should within numbers, but otherwise not match + // Mix of types should within numbers, but otherwise not match PARSEI("30 < 10.2", *_update[0], False); PARSEI("10.2 < 30", *_update[0], True); PARSEI("30 < \"foo\"", *_update[0], Invalid); @@ -1112,17 +1145,18 @@ void DocumentSelectParserTest::testDocumentUpdates1() PARSEI("14.3 == null", *_update[0], False); PARSEI("null = 0", *_update[0], False); - // Field values + // Field values PARSE("testdoctype1.headerval = 24", *_update[0], Invalid); PARSE("testdoctype1.hfloatval = 2.0", *_update[0], Invalid); PARSE("testdoctype1.content = \"bar\"", *_update[0], Invalid); PARSE("testdoctype1.hstringval == testdoctype1.content", *_update[0], Invalid); - // Document types + // Document types PARSE("testdoctype1", *_update[0], True); + PARSE("(testdoctype1)", *_update[0], True); PARSE("testdoctype2", *_update[0], False); - // Inherited doctypes + // Inherited doctypes PARSE("testdoctype2", *_update[4], True); PARSE("testdoctype2", *_update[3], False); PARSE("testdoctype1", *_update[4], True); @@ -1133,7 +1167,7 @@ void DocumentSelectParserTest::testDocumentUpdates2() { createDocs(); - // Id values + // Id values PARSEI("id == \"doc:myspace:anything\"", *_update[0], True); PARSEI(" iD== \"doc:myspace:anything\" ", *_update[0], True); PARSEI("id == \"doc:myspa:nything\"", *_update[0], False); @@ -1159,7 +1193,7 @@ void DocumentSelectParserTest::testDocumentUpdates3() { createDocs(); - // Branch operators + // Branch operators PARSEI("true and false", *_update[0], False); PARSEI("true and true", *_update[0], True); PARSEI("true or false", *_update[0], True); @@ -1171,7 +1205,7 @@ void DocumentSelectParserTest::testDocumentUpdates3() PARSEI("true and not false or false", *_update[0], True); PARSEI("((243 < 300) and (\"FOO\".lowercase() == \"foo\"))", *_update[0], True); - // Invalid branching. testdoctype1.content = 1 is invalid + // Invalid branching. testdoctype1.content = 1 is invalid PARSE("testdoctype1.content = 1 and true", *_update[0], Invalid); PARSE("testdoctype1.content = 1 or true", *_update[0], True); PARSE("testdoctype1.content = 1 and false", *_update[0], False); @@ -1186,7 +1220,7 @@ void DocumentSelectParserTest::testDocumentUpdates4() { createDocs(); - // Functions + // Functions PARSEI("\"bar\".hash() == -2012135647395072713", *_update[0], True); PARSEI("\"bar\".hash().abs() == 2012135647395072713", *_update[0], True); PARSEI("null.hash() == 123", *_update[0], Invalid); @@ -1195,7 +1229,7 @@ void DocumentSelectParserTest::testDocumentUpdates4() PARSEI("\"foo\".hash() == 123", *_update[0], False); PARSEI("(234).hash() == 123", *_update[0], False); - // Arithmetics + // Arithmetics PARSEI("id.specific.hash() % 10 = 8", *_update[0], True); PARSEI("id.specific.hash() % 10 = 2", *_update[0], False); PARSEI("\"foo\" + \"bar\" = \"foobar\"", *_update[0], True); @@ -1221,7 +1255,7 @@ void DocumentSelectParserTest::testUtf8() } std::unique_ptr<select::FieldValueNode> -DocumentSelectParserTest::parseFieldValue(const std::string expression) { +DocumentSelectParserTest::parseFieldValue(const std::string& expression) { return std::unique_ptr<select::FieldValueNode>(dynamic_cast<select::FieldValueNode *>( dynamic_cast<const select::Compare &>(*_parser->parse(expression)).getLeft().clone().release())); } @@ -1246,4 +1280,296 @@ void DocumentSelectParserTest::testThatComplexFieldValuesHaveCorrectFieldNames() parseFieldValue("testdoctype1.headerval.meow.meow{test}")->getRealFieldName()); } +namespace { + +class OperatorVisitor : public select::Visitor { +private: + std::ostringstream data; +public: + void visitConstant(const select::Constant& node) override { + data << node; + } + + void + visitInvalidConstant(const select::InvalidConstant& node) override { + (void) node; + assert(false); + } + + void visitDocumentType(const select::DocType& node) override { + data << "(DOCTYPE " << node << ")"; + } + + void visitComparison(const select::Compare& node) override { + data << '(' << node.getOperator() << ' '; + node.getLeft().visit(*this); + data << ' '; + node.getRight().visit(*this); + data << ')'; + } + + void visitAndBranch(const select::And& node) override { + data << "(AND "; + node.getLeft().visit(*this); + data << " "; + node.getRight().visit(*this); + data << ")"; + } + + void visitOrBranch(const select::Or& node) override { + data << "(OR "; + node.getLeft().visit(*this); + data << " "; + node.getRight().visit(*this); + data << ")"; + } + + void visitNotBranch(const select::Not& node) override { + data << "(NOT "; + node.getChild().visit(*this); + data << ")"; + } + + void visitArithmeticValueNode(const select::ArithmeticValueNode& node) override { + data << '(' << node.getOperatorName() << ' '; + node.getLeft().visit(*this); + data << ' '; + node.getRight().visit(*this); + data << ')'; + } + void visitFunctionValueNode(const select::FunctionValueNode& node) override { + data << '(' << node.getFunctionName() << ' '; + node.getChild().visit(*this); + data << ')'; + } + void visitIdValueNode(const select::IdValueNode& node) override { + data << "(ID " << node.toString() << ')'; + } + void visitFieldValueNode(const select::FieldValueNode& node) override { + data << "(FIELD " << node.getDocType() << ' ' << node.getFieldName() << ')'; + } + void visitFloatValueNode(const select::FloatValueNode& node) override { + data << node.getValue(); + } + void visitVariableValueNode(const select::VariableValueNode& node) override { + data << "(VAR " << node.getVariableName() << ')'; + } + void visitIntegerValueNode(const select::IntegerValueNode& node) override { + data << node.getValue(); + } + void visitCurrentTimeValueNode(const select::CurrentTimeValueNode&) override {} + void visitStringValueNode(const select::StringValueNode& str) override { + data << '"' << str.getValue() << '"'; + } + void visitNullValueNode(const select::NullValueNode&) override { + data << "null"; + } + void visitInvalidValueNode(const select::InvalidValueNode&) override { + data << "INVALID!"; + } + + std::string visit_string() { return data.str(); } +}; + +template <typename NodeType> +std::string node_to_string(const NodeType& node) { + OperatorVisitor v; + node.visit(v); + return v.visit_string(); +} + +} + +std::string DocumentSelectParserTest::parse_to_tree(const std::string& str) { + std::unique_ptr<select::Node> root(_parser->parse(str)); + return node_to_string(*root); +} + +void DocumentSelectParserTest::test_operator_precedence() { + createDocs(); + using namespace std::string_literals; + + CPPUNIT_ASSERT_EQUAL("(AND true false)"s, parse_to_tree("true and false")); + CPPUNIT_ASSERT_EQUAL("(AND (NOT false) true)"s, parse_to_tree("not false and true")); + CPPUNIT_ASSERT_EQUAL("(NOT (AND false true))"s, parse_to_tree("not (false and true)")); + CPPUNIT_ASSERT_EQUAL("(NOT (DOCTYPE testdoctype1))"s, parse_to_tree("not testdoctype1")); + CPPUNIT_ASSERT_EQUAL("(NOT (DOCTYPE (testdoctype1)))"s, parse_to_tree("not (testdoctype1)")); + CPPUNIT_ASSERT_EQUAL("(NOT (DOCTYPE (testdoctype1)))"s, parse_to_tree("(not (testdoctype1))")); + CPPUNIT_ASSERT_EQUAL("(OR (== 1 2) (== 3 4))"s, parse_to_tree("1==2 or 3==4")); + CPPUNIT_ASSERT_EQUAL("(!= (+ (+ 1 2) 3) 0)"s, parse_to_tree("1+2+3 != 0")); + CPPUNIT_ASSERT_EQUAL("(!= (+ (+ 1.1 2.2) 3.3) 4.4)"s, parse_to_tree("1.1+2.2+3.3 != 4.4")); + CPPUNIT_ASSERT_EQUAL("(!= (- (- 1 2) 3) 0)"s, parse_to_tree("1-2-3 != 0")); + CPPUNIT_ASSERT_EQUAL("(!= (+ (+ 1 2) 3) 0)"s, parse_to_tree("1 + 2 + 3 != 0")); + CPPUNIT_ASSERT_EQUAL("(!= (+ 1 (* 2 3)) 0)"s, parse_to_tree("1 + 2 * 3 != 0")); + CPPUNIT_ASSERT_EQUAL("(!= (- (/ (* 1 2) 3) 4) 0)"s, parse_to_tree("1 * 2 / 3 - 4 != 0")); + CPPUNIT_ASSERT_EQUAL("(!= (/ (* 1 2) (- 3 4)) 0)"s, parse_to_tree("1 * 2 / (3 - 4) != 0")); + CPPUNIT_ASSERT_EQUAL("(OR (AND true (NOT (== 1 2))) false)"s, + parse_to_tree("true and not 1 == 2 or false")); + CPPUNIT_ASSERT_EQUAL("(AND (AND (AND (< 1 2) (> 3 4)) (<= 5 6)) (>= 7 8))"s, + parse_to_tree("1 < 2 and 3 > 4 and 5 <= 6 and 7 >= 8")); + CPPUNIT_ASSERT_EQUAL("(OR (AND (AND (< 1 2) (> 3 4)) (<= 5 6)) (>= 7 8))"s, + parse_to_tree("1 < 2 and 3 > 4 and 5 <= 6 or 7 >= 8")); + CPPUNIT_ASSERT_EQUAL("(OR (AND (< 1 2) (> 3 4)) (AND (<= 5 6) (>= 7 8)))"s, + parse_to_tree("1 < 2 and 3 > 4 or 5 <= 6 and 7 >= 8")); + // Unary plus is simply ignored by the parser. + CPPUNIT_ASSERT_EQUAL("(== 1 -2)"s, parse_to_tree("+1==-2")); + CPPUNIT_ASSERT_EQUAL("(== 1.23 -2.56)"s, parse_to_tree("+1.23==-2.56")); + CPPUNIT_ASSERT_EQUAL("(== (+ 1 2) (- 3 -4))"s, parse_to_tree("1 + +2==3 - -4")); + CPPUNIT_ASSERT_EQUAL("(== (+ 1 2) (- 3 -4))"s, parse_to_tree("1++2==3--4")); + + // Due to the way parentheses are handled by the AST, ((foo)) always gets + // reduced down to (foo). + CPPUNIT_ASSERT_EQUAL("(DOCTYPE (testdoctype1))"s, parse_to_tree("(((testdoctype1)))")); + CPPUNIT_ASSERT_EQUAL("(AND (DOCTYPE (testdoctype1)) (DOCTYPE (testdoctype2)))"s, + parse_to_tree("((((testdoctype1))) and ((testdoctype2)))")); + + CPPUNIT_ASSERT_EQUAL("(== (ID id) \"foo\")"s, parse_to_tree("id == 'foo'")); + CPPUNIT_ASSERT_EQUAL("(== (ID id.group) \"foo\")"s, parse_to_tree("id.group == 'foo'")); + // id_spec function apply + CPPUNIT_ASSERT_EQUAL("(== (hash (ID id)) 12345)"s, parse_to_tree("id.hash() == 12345")); + // Combination of id_spec function apply and arith_expr function apply + CPPUNIT_ASSERT_EQUAL("(== (abs (hash (ID id))) 12345)"s, parse_to_tree("id.hash().abs() == 12345")); +} + +void DocumentSelectParserTest::test_token_used_as_ident_preserves_casing() { + createDocs(); + using namespace std::string_literals; + + // TYPE, SCHEME, ORDER etc are tokens that may also be used as identifiers + // without introducing parsing ambiguities. In this context their original + // casing should be preserved. + CPPUNIT_ASSERT_EQUAL("(== (VAR Type) 123)"s, parse_to_tree("$Type == 123")); + CPPUNIT_ASSERT_EQUAL("(== (VAR giD) 123)"s, parse_to_tree("$giD == 123")); + CPPUNIT_ASSERT_EQUAL("(== (VAR ORDER) 123)"s, parse_to_tree("$ORDER == 123")); +} + +void DocumentSelectParserTest::test_ambiguous_field_spec_expression_is_handled_correctly() { + createDocs(); + using namespace std::string_literals; + // In earlier revisions of LR(1)-grammar, this triggered a reduce/reduce conflict between + // logical_expr and arith_expr for the sequence '(' field_spec ')', which failed to + // parse in an expected manner. Test that we don't get regressions here. + CPPUNIT_ASSERT_EQUAL("(!= (FIELD testdoctype1 foo) null)"s, parse_to_tree("(testdoctype1.foo)")); + CPPUNIT_ASSERT_EQUAL("(AND (!= (FIELD testdoctype1 foo) null) (!= (FIELD testdoctype1 bar) null))"s, + parse_to_tree("(testdoctype1.foo) AND (testdoctype1.bar)")); +} + +void DocumentSelectParserTest::test_can_build_field_value_from_field_expr_node() { + using select::FieldExprNode; + { + // Simple field expression + auto lhs = std::make_unique<FieldExprNode>("mydoctype"); + auto root = std::make_unique<FieldExprNode>(std::move(lhs), "foo"); + auto fv = root->convert_to_field_value(); + CPPUNIT_ASSERT_EQUAL(vespalib::string("mydoctype"), fv->getDocType()); + CPPUNIT_ASSERT_EQUAL(vespalib::string("foo"), fv->getFieldName()); + } + { + // Nested field expression + auto lhs1 = std::make_unique<FieldExprNode>("mydoctype"); + auto lhs2 = std::make_unique<FieldExprNode>(std::move(lhs1), "foo"); + auto root = std::make_unique<FieldExprNode>(std::move(lhs2), "bar"); + auto fv = root->convert_to_field_value(); + CPPUNIT_ASSERT_EQUAL(vespalib::string("mydoctype"), fv->getDocType()); + CPPUNIT_ASSERT_EQUAL(vespalib::string("foo.bar"), fv->getFieldName()); + } +} + +void DocumentSelectParserTest::test_can_build_function_call_from_field_expr_node() { + using select::FieldExprNode; + { + // doctype.foo.lowercase() + // Note that calling lowercase() directly on the doctype is not supported + // (see test_function_call_on_doctype_throws_exception) + auto lhs1 = std::make_unique<FieldExprNode>("mydoctype"); + auto lhs2 = std::make_unique<FieldExprNode>(std::move(lhs1), "foo"); + auto root = std::make_unique<FieldExprNode>(std::move(lhs2), "lowercase"); + auto func = root->convert_to_function_call(); + CPPUNIT_ASSERT_EQUAL(vespalib::string("lowercase"), func->getFunctionName()); + // TODO vespalib::string? + CPPUNIT_ASSERT_EQUAL(std::string("(FIELD mydoctype foo)"), node_to_string(func->getChild())); + } +} + +void DocumentSelectParserTest::test_function_call_on_doctype_throws_exception() { + using select::FieldExprNode; + auto lhs = std::make_unique<FieldExprNode>("mydoctype"); + auto root = std::make_unique<FieldExprNode>(std::move(lhs), "lowercase"); + try { + root->convert_to_function_call(); + } catch (const vespalib::IllegalArgumentException& e) { + CPPUNIT_ASSERT_EQUAL(vespalib::string("Cannot call function 'lowercase' directly on document type"), + e.getMessage()); + } +} + +namespace { + +void check_parse_i64(vespalib::stringref str, bool expect_ok, int64_t expected_output) { + int64_t out = 0; + bool ok = select::util::parse_i64(str.data(), str.size(), out); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Parsing did not returned expected success status for i64 input " + str, expect_ok, ok); + if (expect_ok) { + CPPUNIT_ASSERT_EQUAL_MESSAGE("Parse output not as expected for i64 input " + str, expected_output, out); + } +} + +void check_parse_hex_i64(vespalib::stringref str, bool expect_ok, int64_t expected_output) { + int64_t out = 0; + bool ok = select::util::parse_hex_i64(str.data(), str.size(), out); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Parsing did not returned expected success status for hex i64 input " + str, expect_ok, ok); + if (expect_ok) { + CPPUNIT_ASSERT_EQUAL_MESSAGE("Parse output not as expected for hex i64 input " + str, expected_output, out); + } +} + +void check_parse_double(vespalib::stringref str, bool expect_ok, double expected_output) { + double out = 0; + bool ok = select::util::parse_double(str.data(), str.size(), out); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Parsing did not returned expected success status for hex i64 input " + str, expect_ok, ok); + if (expect_ok) { + CPPUNIT_ASSERT_EQUAL_MESSAGE("Parse output not as expected for double input " + str, expected_output, out); + } +} + +} + +void DocumentSelectParserTest::test_parse_utilities_handle_well_formed_input() { + check_parse_i64("0", true, 0); + check_parse_i64("1", true, 1); + check_parse_i64("9223372036854775807", true, INT64_MAX); + + // Note: 0x prefix is _not_ included + check_parse_hex_i64("0", true, 0); + check_parse_hex_i64("1", true, 1); + check_parse_hex_i64("f", true, 15); + check_parse_hex_i64("F", true, 15); + check_parse_hex_i64("ffffffff", true, UINT32_MAX); + check_parse_hex_i64("7FFFFFFFFFFFFFFF", true, INT64_MAX); + // We actually parse as u64 internally, then convert + check_parse_hex_i64("ffffffffffffffff", true, -1); + + check_parse_double("1.0", true, 1.0); + check_parse_double("1.", true, 1.0); + check_parse_double("1.79769e+308", true, 1.79769e+308); // DBL_MAX +} + +void DocumentSelectParserTest::test_parse_utilities_handle_malformed_input() { + check_parse_i64("9223372036854775808", false, 0); // INT64_MAX + 1 + check_parse_i64("18446744073709551615", false, 0); // UINT64_MAX + check_parse_i64("", false, 0); + check_parse_i64("bjarne", false, 0); + check_parse_i64("1x", false, 0); + + check_parse_hex_i64("", false, 0); + check_parse_hex_i64("g", false, 0); + check_parse_hex_i64("0x1", false, 0); + check_parse_hex_i64("ffffffffffffffff1", false, 0); + + check_parse_double("1.x", false, 0.0); + // TODO double outside representable range returns Inf, but we probably would + // like this to trigger a parse failure? + check_parse_double("1.79769e+309", true, std::numeric_limits<double>::infinity()); +} + } // document diff --git a/document/src/vespa/document/bucket/bucketselector.cpp b/document/src/vespa/document/bucket/bucketselector.cpp index 5ded691269a..ceb231a483c 100644 --- a/document/src/vespa/document/bucket/bucketselector.cpp +++ b/document/src/vespa/document/bucket/bucketselector.cpp @@ -137,27 +137,6 @@ using namespace document::select; } } - void compare(const select::SearchColumnValueNode& node, - const select::ValueNode& valnode, - const select::Operator& op) { - if (op == FunctionOperator::EQ || op == document::select::GlobOperator::GLOB) { - int bucketCount = 1 << 16; - const IntegerValueNode* val( - dynamic_cast<const IntegerValueNode*>(&valnode)); - - int64_t rval = val->getValue(); - - for (int i = 0; i < bucketCount; i++) { - int64_t column = node.getValue(BucketId(16, i)); - if (column == rval) { - _buckets.push_back(BucketId(16, i)); - } - } - - _unknown = false; - } - } - void visitComparison(const document::select::Compare& node) override { if (node.getOperator() != document::select::FunctionOperator::EQ && node.getOperator() != document::select::GlobOperator::GLOB) @@ -166,12 +145,8 @@ using namespace document::select; } const IdValueNode* lid(dynamic_cast<const IdValueNode*>( &node.getLeft())); - const SearchColumnValueNode* sc(dynamic_cast<const SearchColumnValueNode*>( - &node.getLeft())); if (lid) { compare(*lid, node.getRight(), node.getOperator()); - } else if (sc) { - compare(*sc, node.getRight(), node.getOperator()); } else { const IdValueNode* rid(dynamic_cast<const IdValueNode*>( &node.getRight())); @@ -187,7 +162,6 @@ using namespace document::select; void visitArithmeticValueNode(const ArithmeticValueNode &) override {} void visitFunctionValueNode(const FunctionValueNode &) override {} void visitIdValueNode(const IdValueNode &) override {} - void visitSearchColumnValueNode(const SearchColumnValueNode &) override {} void visitFieldValueNode(const FieldValueNode &) override {} void visitFloatValueNode(const FloatValueNode &) override {} void visitVariableValueNode(const VariableValueNode &) override {} diff --git a/document/src/vespa/document/select/.gitignore b/document/src/vespa/document/select/.gitignore index 5f004816692..919eb5c7ca9 100644 --- a/document/src/vespa/document/select/.gitignore +++ b/document/src/vespa/document/select/.gitignore @@ -2,3 +2,6 @@ Makefile .depend* .*.swp *.So +*.hxx +*.cxx +*.hh diff --git a/document/src/vespa/document/select/CMakeLists.txt b/document/src/vespa/document/select/CMakeLists.txt index 0e94fa0f530..6dadd35e98a 100644 --- a/document/src/vespa/document/select/CMakeLists.txt +++ b/document/src/vespa/document/select/CMakeLists.txt @@ -1,4 +1,14 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +find_package(BISON REQUIRED) +find_package(FLEX REQUIRED) + +BISON_TARGET(DocSelParser grammar/parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.cxx) +FLEX_TARGET(DocSelLexer grammar/lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/lexer.cxx) + +ADD_FLEX_BISON_DEPENDENCY(DocSelLexer DocSelParser) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + vespa_add_library(document_select OBJECT SOURCES bodyfielddetector.cpp @@ -13,7 +23,6 @@ vespa_add_library(document_select OBJECT operator.cpp orderingselector.cpp orderingspecification.cpp - parser.cpp result.cpp resultset.cpp resultlist.cpp @@ -22,6 +31,14 @@ vespa_add_library(document_select OBJECT value.cpp valuenode.cpp valuenodes.cpp + parser.cpp + parse_utils.cpp + parsing_failed_exception.cpp + ${BISON_DocSelParser_OUTPUTS} + ${FLEX_DocSelLexer_OUTPUTS} AFTER document_documentconfig ) + +#TODO Remove once we have a recently new flex compiler. At least 2.5.38/39 or 2.6 +set_source_files_properties(${FLEX_DocSelLexer_OUTPUTS} PROPERTIES COMPILE_FLAGS -Wno-register) diff --git a/document/src/vespa/document/select/branch.cpp b/document/src/vespa/document/select/branch.cpp index 7f6ad252471..5b28fad0df8 100644 --- a/document/src/vespa/document/select/branch.cpp +++ b/document/src/vespa/document/select/branch.cpp @@ -9,7 +9,7 @@ namespace document { namespace select { And::And(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* name) - : Branch(name ? name : "AND"), + : Branch(name ? name : "and"), _left(std::move(left)), _right(std::move(right)) { @@ -55,7 +55,7 @@ And::trace(const Context& context, std::ostream& out) const } Or::Or(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* name) - : Branch(name ? name : "OR"), + : Branch(name ? name : "or"), _left(std::move(left)), _right(std::move(right)) { @@ -101,7 +101,7 @@ Or::trace(const Context& context, std::ostream& out) const } Not::Not(std::unique_ptr<Node> child, const char* name) - : Branch(name ? name : "NOT"), + : Branch(name ? name : "not"), _child(std::move(child)) { assert(_child.get()); diff --git a/document/src/vespa/document/select/cloningvisitor.cpp b/document/src/vespa/document/select/cloningvisitor.cpp index 4011cbdeea1..d695e3ec83d 100644 --- a/document/src/vespa/document/select/cloningvisitor.cpp +++ b/document/src/vespa/document/select/cloningvisitor.cpp @@ -162,7 +162,7 @@ CloningVisitor::visitConstant(const Constant &expr) _priority = ConstPriority; bool val = expr.getConstantValue(); _resultSet.add(val ? Result::True : Result::False); - _node.reset(new Constant(val ? "true" : "false")); + _node.reset(new Constant(val)); } @@ -199,16 +199,6 @@ CloningVisitor::visitIdValueNode(const IdValueNode &expr) void -CloningVisitor::visitSearchColumnValueNode(const SearchColumnValueNode &expr) -{ - _constVal = false; - ++_fieldNodes; // needs document id, thus needs document - _valueNode = expr.clone(); - _priority = SearchColPriority; -} - - -void CloningVisitor::visitFieldValueNode(const FieldValueNode &expr) { _constVal = false; diff --git a/document/src/vespa/document/select/cloningvisitor.h b/document/src/vespa/document/select/cloningvisitor.h index ff74af1201d..2c5f94c20a4 100644 --- a/document/src/vespa/document/select/cloningvisitor.h +++ b/document/src/vespa/document/select/cloningvisitor.h @@ -60,7 +60,6 @@ public: void visitInvalidConstant(const InvalidConstant &expr) override; void visitDocumentType(const DocType &expr) override; void visitIdValueNode(const IdValueNode &expr) override; - void visitSearchColumnValueNode(const SearchColumnValueNode &expr) override; void visitFieldValueNode(const FieldValueNode &expr) override; void visitFloatValueNode(const FloatValueNode &expr) override; void visitVariableValueNode(const VariableValueNode &expr) override; diff --git a/document/src/vespa/document/select/constant.cpp b/document/src/vespa/document/select/constant.cpp index 02821180337..8d1445f40a0 100644 --- a/document/src/vespa/document/select/constant.cpp +++ b/document/src/vespa/document/select/constant.cpp @@ -7,28 +7,10 @@ namespace document::select { -Constant::Constant(const vespalib::stringref & value) - : Node(value), - _value(false) +Constant::Constant(bool value) + : Node(value ? "true" : "false"), // TODO remove required name from Node + _value(value) { - if (value.size() == 4 && - (value[0] & 0xdf) == 'T' && - (value[1] & 0xdf) == 'R' && - (value[2] & 0xdf) == 'U' && - (value[3] & 0xdf) == 'E') - { - _value = true; - } else if (value.size() == 5 && - (value[0] & 0xdf) == 'F' && - (value[1] & 0xdf) == 'A' && - (value[2] & 0xdf) == 'L' && - (value[3] & 0xdf) == 'S' && - (value[4] & 0xdf) == 'E') - { - _value = false; - } else { - assert(false); - } } ResultList diff --git a/document/src/vespa/document/select/constant.h b/document/src/vespa/document/select/constant.h index 08be5c95ec7..46a98ed7eaa 100644 --- a/document/src/vespa/document/select/constant.h +++ b/document/src/vespa/document/select/constant.h @@ -5,9 +5,8 @@ * * @brief Class describing a constant in the select tree. * - * @author H�kon Humberset + * @author Håkon Humberset * @date 2005-06-07 - * @version $Id$ */ #pragma once @@ -23,7 +22,7 @@ private: bool _value; public: - explicit Constant(const vespalib::stringref & value); + explicit Constant(bool value); ResultList contains(const Context&) const override { return ResultList(Result::get(_value)); @@ -32,8 +31,8 @@ public: ResultList trace(const Context&, std::ostream& trace) const override; void print(std::ostream& out, bool verbose, const std::string& indent) const override; void visit(Visitor& v) const override; - bool getConstantValue() const { return _value; } - Node::UP clone() const override { return wrapParens(new Constant(_name)); } + bool getConstantValue() const noexcept { return _value; } + Node::UP clone() const override { return wrapParens(new Constant(_value)); } }; diff --git a/document/src/vespa/document/select/gid_filter.cpp b/document/src/vespa/document/select/gid_filter.cpp index ce3045564ba..71a57a0886a 100644 --- a/document/src/vespa/document/select/gid_filter.cpp +++ b/document/src/vespa/document/select/gid_filter.cpp @@ -22,7 +22,6 @@ struct NoOpVisitor : Visitor { void visitArithmeticValueNode(const ArithmeticValueNode&) override {} void visitFunctionValueNode(const FunctionValueNode&) override {} void visitIdValueNode(const IdValueNode&) override {} - void visitSearchColumnValueNode(const SearchColumnValueNode&) override {} void visitFieldValueNode(const FieldValueNode&) override {} void visitFloatValueNode(const FloatValueNode&) override {} void visitVariableValueNode(const VariableValueNode&) override {} diff --git a/document/src/vespa/document/select/grammar/lexer.ll b/document/src/vespa/document/select/grammar/lexer.ll new file mode 100644 index 00000000000..8cd5638c122 --- /dev/null +++ b/document/src/vespa/document/select/grammar/lexer.ll @@ -0,0 +1,182 @@ + /* Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. */ + + /* We use the .*xx-suffix to denote a build-time generated file */ +%option outfile="lexer.cxx" +%option header-file="lexer.hxx" + +%option c++ + /* Uncomment to enable debug tracing of parsing */ + /* %option debug */ +%option 8bit warn nodefault +%option noyywrap nounput +%option yyclass="document::select::DocSelScanner" + + /* Used to track source locations, see https://github.com/bingmann/flex-bison-cpp-example/blob/master/src/scanner.ll */ +%{ +#define YY_USER_ACTION yyloc->columns(yyleng); +%} + +%{ + +#include "parser.hxx" +#include <vespa/document/select/scanner.h> +#include <vespa/document/select/parse_utils.h> +#include <vespa/document/util/stringutil.h> +#include <vespa/vespalib/stllike/string.h> +#include <string> +#include <cstdlib> + +#undef YY_DECL +#define YY_DECL int document::select::DocSelScanner::yylex( \ + document::select::DocSelParser::semantic_type* yylval, \ + document::select::DocSelParser::location_type* yyloc) + +using token = document::select::DocSelParser::token; +using string = vespalib::string; + +// Inspired by https://coldfix.eu/2015/05/16/bison-c++11/ + +#define YIELD_TOKEN(name, field_name, value) \ + yylval->field_name = value; \ + return token::T_##name; + +#define INT_TOKEN(name, value) YIELD_TOKEN(name, i64_val, value) +#define STRING_TOKEN(name) YIELD_TOKEN(name, string_val, new string(yytext, yyleng)) +#define CONST_STR_TOKEN(name, value) YIELD_TOKEN(name, const_str_val, value) +#define TAGGED_TOKEN INT_TOKEN + +#define NAMED_TOKEN(name) return token::T_##name; + +%} + + /* Lexer fragments, used as part of token patterns */ + +SIGN [+-] +DECIMAL [0-9]+ +HEXDIGIT [0-9a-fA-F] +HEX 0[xX]{HEXDIGIT}{1,16} +OCTAL 0[0-7]* +EXPONENT [eE][+-]?[0-9]+ +IDCHARS [a-zA-Z_][a-zA-Z_0-9_]* +WS [ \f\r\t] + + /* It is weird that you can't do \' inside "" and vice versa, but that's the StringUtil::unescape logic today... */ +DQ_STRING \"(\\([\\tnfr"]|x{HEXDIGIT}{2})|[^"\\])*\" +SQ_STRING \'(\\([\\tnfr']|x{HEXDIGIT}{2})|[^'\\])*\' + +%% + + /* Code to take place at the beginning of yylex() */ +%{ + // TODO move to YY_USER_ACTION instead? + yyloc->step(); +%} + + /* TODO support length suffixes? supported in JavaCC grammar, but not in legacy Spirit grammar... */ +{HEX} { + // TODO replace with std::from_string() once compiler support is there + if (!util::parse_hex_i64(yytext + 2, yyleng - 2, yylval->i64_val)) { // Skip 0[xX] prefix + throw_parser_syntax_error(*yyloc, "Not a valid 64-bit hex integer: " + std::string(yytext, yyleng)); + } + return token::T_INTEGER; +} + + /* Sign is handled explicitly in the parser to avoid lexing ambiguities for expressions such as "1 -2" */ +{DECIMAL} { + if (!util::parse_i64(yytext, yyleng, yylval->i64_val)) { + throw_parser_syntax_error(*yyloc, "Not a valid signed 64-bit integer: " + std::string(yytext, yyleng)); + } + return token::T_INTEGER; +} + + /* + * We use a strict definition of floats when lexing, i.e. we require a dot + * in order to remove ambiguities with the base 10 integer token. + */ +[0-9]+(\.[0-9]*){EXPONENT}?[fFdD]? { + if (!util::parse_double(yytext, yyleng, yylval->double_val)) { + throw_parser_syntax_error(*yyloc, "Not a valid floating point number: " + std::string(yytext, yyleng)); + } + return token::T_FLOAT; +} + +({DQ_STRING}|{SQ_STRING}) { + // Always slice off start and end quote chars + yylval->string_val = new string(yytext + 1, yyleng - 2); + return token::T_STRING; +} + + /* FIXME this is a syntactic hack to "flatten" fieldpath map and array lookups into a single token + rather than match these structurally in the parser itself. This is due to the way fieldpaths + are handled in the legacy AST (i.e. as strings, not structures), and this must be changed first + before we can fix this. */ + /* Field path expressions do not support any other escapes than double quote char */ + /* TODO {WS} does not include newline, do we need to support that here? */ +\{{WS}*($?{IDCHARS}|{DECIMAL}|\"([^\\\"]|\\\")*\"){WS}*\} STRING_TOKEN(FP_MAP_LOOKUP) +\[{WS}*(${IDCHARS}|{DECIMAL}){WS}*\] STRING_TOKEN(FP_ARRAY_LOOKUP) + + /* Primary tokens are case insensitive */ +(?i:"id") NAMED_TOKEN(ID) +(?i:"null") NAMED_TOKEN(NULL) +(?i:"true") NAMED_TOKEN(TRUE) +(?i:"false") NAMED_TOKEN(FALSE) +(?i:"and") NAMED_TOKEN(AND) +(?i:"or") NAMED_TOKEN(OR) +(?i:"not") NAMED_TOKEN(NOT) + + /* We expose the verbatim input as the token value, as these may also be used for identifiers... */ +(?i:"user") STRING_TOKEN(USER) +(?i:"group") STRING_TOKEN(GROUP) +(?i:"scheme") STRING_TOKEN(SCHEME) +(?i:"namespace") STRING_TOKEN(NAMESPACE) +(?i:"specific") STRING_TOKEN(SPECIFIC) +(?i:"bucket") STRING_TOKEN(BUCKET) +(?i:"gid") STRING_TOKEN(GID) +(?i:"type") STRING_TOKEN(TYPE) +(?i:"order") STRING_TOKEN(ORDER) + +"now\(\)" NAMED_TOKEN(NOW_FUNC) /* This _is_ case-sensitive in the legacy parser */ + + /* Binary operators */ + /* TODO INT_TOKEN with code directly from selection operator node? Or direct operator object ptr? */ +"=" NAMED_TOKEN(GLOB) +"=~" NAMED_TOKEN(REGEX) +"==" NAMED_TOKEN(EQ) +"!=" NAMED_TOKEN(NE) +">=" NAMED_TOKEN(GE) +"<=" NAMED_TOKEN(LE) +">" NAMED_TOKEN(GT) +"<" NAMED_TOKEN(LT) + +"$" NAMED_TOKEN(DOLLAR) +"." NAMED_TOKEN(DOT) +"(" NAMED_TOKEN(LPAREN) +")" NAMED_TOKEN(RPAREN) +"," NAMED_TOKEN(COMMA) +"+" NAMED_TOKEN(PLUS) +"-" NAMED_TOKEN(MINUS) +"*" NAMED_TOKEN(MULTIPLY) +"/" NAMED_TOKEN(DIVIDE) +"%" NAMED_TOKEN(MODULO) + +{IDCHARS} STRING_TOKEN(IDENTIFIER) + +\n { + yyloc->lines(yyleng); + yyloc->step(); + return yytext[0]; +} + +{WS} { + yyloc->step(); +} + + /* + * Everything that hasn't already matched is an error. Throw exception immediately with the exact + * char to avoid getting auto-generated error messages with "unexpected $undefined" due to the + * resulting token not matching any existing, explicitly named tokens. + */ +. { throw_parser_syntax_error(*yyloc, "Unexpected character: '" + StringUtil::escape(vespalib::string(yytext, 1)) + "'"); } + +%% + diff --git a/document/src/vespa/document/select/grammar/parser.yy b/document/src/vespa/document/select/grammar/parser.yy new file mode 100644 index 00000000000..baf987355c9 --- /dev/null +++ b/document/src/vespa/document/select/grammar/parser.yy @@ -0,0 +1,374 @@ + /* Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. */ + +%output "parser.cxx" +%defines "parser.hxx" + + /* Skeleton implementation included as part of the generated source. Note: _not_ covered by the GPL. */ +%skeleton "lalr1.cc" + +%require "3.0" + + /* Uncomment to enable debugging of lexer invocations */ + /*%debug*/ + +%locations + +%define parse.error verbose +%define parse.assert + +%define api.token.prefix {T_} +%define api.namespace {document::select} +%define parser_class_name {DocSelParser} + + /* + * Due to current Bison variant support not being move-enabled (and our AST ptrs being move-only), + * we have to use good old POD unions for our rule results. Note that we have to use %destructor + * for all ptrs to ensure cleanup. + */ +%union { + int64_t i64_val; + double double_val; + const char* const_str_val; + vespalib::string* string_val; + Constant* constant_node; + ValueNode* value_node; + FieldExprNode* field_expr_node; + Node* abstract_node; +} + +%token END 0 "end of input" +%token NULL +%token TRUE +%token FALSE +%token AND +%token OR +%token NOT + + /* Specify aliases for several tokens for ease of use and better error reporting */ +%token GLOB "=" +%token REGEX "=~" +%token EQ "==" +%token NE "!=" +%token GE ">=" +%token LE "<=" +%token GT ">" +%token LT "<" +%token ID +%token NOW_FUNC + + /* + * Tokens that we only mention by alias in the grammar rules, but which we define + * explicitly to improve error reporting + */ +%token DOLLAR "$" +%token DOT "." +%token LPAREN "(" +%token RPAREN ")" +%token COMMA "," +%token PLUS "+" +%token MINUS "-" +%token MULTIPLY "*" +%token DIVIDE "/" +%token MODULO "%" + +%token <string_val> IDENTIFIER +%token <string_val> STRING +%token <string_val> FP_MAP_LOOKUP FP_ARRAY_LOOKUP +%token <double_val> FLOAT +%token <i64_val> INTEGER +%token <string_val> USER GROUP SCHEME NAMESPACE SPECIFIC BUCKET GID TYPE ORDER + +%type <string_val> ident mangled_ident +%type <abstract_node> bool_ + /* TODO 'leaf' is a bad name for something that isn't a leaf... */ +%type <abstract_node> expression comparison logical_expr leaf doc_type +%type <string_val> id_arg +%type <value_node> number null_ value string arith_expr id_spec variable +%type <field_expr_node> field_spec + +%destructor { delete $$; } IDENTIFIER STRING FP_MAP_LOOKUP FP_ARRAY_LOOKUP +%destructor { delete $$; } USER GROUP SCHEME NAMESPACE SPECIFIC BUCKET GID TYPE ORDER +%destructor { delete $$; } null_ bool_ number string doc_type ident id_arg id_spec +%destructor { delete $$; } variable mangled_ident field_spec value arith_expr +%destructor { delete $$; } comparison leaf logical_expr expression + +%start entry + +%parse-param {DocSelScanner& scanner} +%parse-param {const BucketIdFactory& bucket_id_factory} +%parse-param {const DocumentTypeRepo& doc_type_repo} +%parse-param {std::unique_ptr<Node>& recv_expr} + + /* Generated parser header file verbatim */ +%code requires { + +#include "location.hh" +#include <vespa/document/select/constant.h> +#include <vespa/document/select/branch.h> +#include <vespa/document/select/compare.h> +#include <vespa/document/select/valuenodes.h> +#include <vespa/vespalib/stllike/string.h> +#include <memory> + +namespace document { +class BucketIdFactory; +class DocumentTypeRepo; +} + +namespace document::select { +class DocSelScanner; +class Node; +class Constant; +class ValueNode; +} + +} + +%code { + +// Bison has some chunky destructors that trigger inlining warnings. Disable warning +// for this translation unit, since we can't really do much about the code it generates. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winline" + +#include <vespa/document/bucket/bucketidfactory.h> +#include <vespa/document/repo/documenttyperepo.h> +#include <vespa/document/select/scanner.h> +#include <vespa/document/select/constant.h> +#include <vespa/document/select/branch.h> +#include <vespa/document/select/compare.h> +#include <vespa/document/select/doctype.h> +#include <vespa/document/select/valuenodes.h> +#include <vespa/document/util/stringutil.h> +#include <vespa/vespalib/util/exceptions.h> +#include <string> +#include <iostream> +#include <sstream> +#include <memory> + +using string = vespalib::string; + +// Wrap grabbing pointers from sub-rules in a way that nulls out the +// stored attribute from the Bison stack. Otherwise, exception cleanup +// in the parser code will attempt to double-delete the pointee. +// Yes, it's not beautiful, but that's life when you're dealing with raw pointers. +template <typename T> +std::unique_ptr<T> steal(T*& ptr) noexcept { + std::unique_ptr<T> owned(ptr); + ptr = nullptr; + return owned; +} + +// yylex tokenization must defer to scanner instance given to parser +#undef yylex +#define yylex scanner.yylex + +} + +%code provides { + +// This cute little indirection is to get around the syntax_error constructor +// being defined as inline and therefore not being available outside the +// auto-generated parser source file. +[[noreturn]] void throw_parser_syntax_error(const document::select::DocSelParser::location_type& loc, + const std::string& msg); + +} + + +%left OR +%left AND +%left EQ NE LT GT LE GE GLOB REGEX +%left PLUS MINUS +%left MULTIPLY DIVIDE +%left MODULO /* Matches legacy parser recursive descent precedence */ +%precedence NEG +%right UNOT +%left NON_DOT +%precedence DOT /* Used to give higher precedence to id.foo vs id expressions. Re: "dangling else" problem */ + +%% + +null_ + : NULL { $$ = new NullValueNode(); } + ; + +bool_ + : TRUE { $$ = new Constant(true); } + | FALSE { $$ = new Constant(false); } + ; + +number + : INTEGER { $$ = new IntegerValueNode($1, false); } + | FLOAT { $$ = new FloatValueNode($1); } + ; + +string + : STRING { { + try { + $$ = new StringValueNode(StringUtil::unescape(*steal<string>($1))); + } catch (const vespalib::IllegalArgumentException& exc) { + throw syntax_error(@$, exc.getMessage()); + } + } } + ; + +doc_type + : ident { + if (doc_type_repo.getDocumentType(*$1) == nullptr) { + throw syntax_error(@$, vespalib::make_string("Document type '%s' not found", $1->c_str())); + } + $$ = new DocType(*steal<string>($1)); + } + ; + +ident + : IDENTIFIER { $$ = $1; } + | SCHEME { $$ = $1; } + | TYPE { $$ = $1; } + | NAMESPACE { $$ = $1; } + | SPECIFIC { $$ = $1; } + | BUCKET { $$ = $1; } + | GID { $$ = $1; } + | ORDER { $$ = $1; } + ; + +id_arg + : USER { $$ = $1; } + | GROUP { $$ = $1; } + | SCHEME { $$ = $1; } + | NAMESPACE { $$ = $1; } + | SPECIFIC { $$ = $1; } + | BUCKET { $$ = $1; } + | GID { $$ = $1; } + | TYPE { $$ = $1; } + ; + +id_spec + : ID %prec NON_DOT { $$ = new IdValueNode(bucket_id_factory, "id", ""); } /* Prefer shifting instead of reducing */ + | ID "." id_arg { $$ = new IdValueNode(bucket_id_factory, "id", *steal<string>($3)); } + | ID "." IDENTIFIER "(" ")" { $$ = new FunctionValueNode(*steal<string>($3), std::make_unique<IdValueNode>(bucket_id_factory, "id", "")); } + | ID "." ORDER "(" INTEGER "," INTEGER ")" { $$ = new IdValueNode(bucket_id_factory, "id", *steal<string>($3), $5, $7); } + ; + +variable + : "$" ident { $$ = new VariableValueNode(*steal<string>($2)); } + ; + + /* FIXME this is a horrible leftover of post-parsed fieldpath processing */ + /* At least we verify structural integrity at initial parse-time now... */ + /* Post-parsing should be replaced with an actual parse-time built AST! */ +mangled_ident + : ident { $$ = $1; } + | mangled_ident FP_MAP_LOOKUP { $1->append(*steal<string>($2)); $$ = $1; } + | mangled_ident FP_ARRAY_LOOKUP { $1->append(*steal<string>($2)); $$ = $1; } + ; + +field_spec + : ident "." mangled_ident { + if (doc_type_repo.getDocumentType(*$1) == nullptr) { + throw syntax_error(@$, vespalib::make_string("Document type '%s' not found", $1->c_str())); + } + $$ = new FieldExprNode(std::make_unique<FieldExprNode>(*steal<string>($1)), *steal<string>($3)); + } + | field_spec "." mangled_ident { $$ = new FieldExprNode(steal<FieldExprNode>($1), *steal<string>($3)); } + ; + +value + : null_ { $$ = $1; } + | string { $$ = $1; } + | id_spec { $$ = $1; } + | variable { $$ = $1; } + | NOW_FUNC { $$ = new CurrentTimeValueNode(); } + ; + +arith_expr + : value { $$ = $1; } + | number { $$ = $1; } + /* JavaCC and legacy parsers don't support unary plus/minus for _expressions_, just for numbers. So we have to fudge this a bit. */ + | "-" number %prec NEG { + if (dynamic_cast<IntegerValueNode*>($2) != nullptr) { + $$ = new IntegerValueNode(- static_cast<IntegerValueNode&>(*steal<ValueNode>($2)).getValue(), false); + } else { + $$ = new FloatValueNode(- dynamic_cast<FloatValueNode&>(*steal<ValueNode>($2)).getValue()); + } + } + | "+" number %prec NEG { $$ = $2; } + | field_spec { $$ = steal<FieldExprNode>($1)->convert_to_field_value().release(); } + | field_spec "(" ")" { $$ = steal<FieldExprNode>($1)->convert_to_function_call().release(); } + | arith_expr "+" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "+", steal<ValueNode>($3)); } + | arith_expr "-" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "-", steal<ValueNode>($3)); } + | arith_expr "*" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "*", steal<ValueNode>($3)); } + | arith_expr "/" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "/", steal<ValueNode>($3)); } + | arith_expr "%" arith_expr { $$ = new ArithmeticValueNode(steal<ValueNode>($1), "%", steal<ValueNode>($3)); } + | "(" arith_expr ")" { $$ = $2; $$->setParentheses(); } + | arith_expr "." IDENTIFIER "(" ")" { $$ = new FunctionValueNode(*steal<string>($3), steal<ValueNode>($1)); } /* FIXME shift/reduce conflict */ + ; + +comparison + : arith_expr EQ arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::EQ, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr NE arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::NE, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr GE arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::GEQ, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr LE arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::LEQ, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr GT arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::GT, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr LT arith_expr { $$ = new Compare(steal<ValueNode>($1), FunctionOperator::LT, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr GLOB arith_expr { $$ = new Compare(steal<ValueNode>($1), GlobOperator::GLOB, steal<ValueNode>($3), bucket_id_factory); } + | arith_expr REGEX arith_expr { $$ = new Compare(steal<ValueNode>($1), RegexOperator::REGEX, steal<ValueNode>($3), bucket_id_factory); } + ; + +leaf + : bool_ { $$ = $1; } + | comparison { $$ = $1; } + | doc_type { $$ = $1; } + | arith_expr { /* Actually field_spec, see comment below..! */ + // Grammar-wise, we _do not_ accept arbitrary arith_exprs at this level. But the + // selection grammar as it stands is otherwise ambiguous with LR(1) parsing. + // More specifically, if we used field_spec instead of arith_expr here, the parser + // state machine cannot decide what to do if it has processed the sequence '(' field_spec + // and sees the next token of ')'. Since both logical_expr and arith_expr allows for + // parenthesis expression recursion, the reduce step may produce either of these and + // is therefore technically undefined. By using arith_expr instead for this rule, all + // '(' field_spec ')' sequences result in an arith_expr rule match and the reduce/reduce + // conflict goes away. We can then do a sneaky "run-time" type check to ensure we only + // get the expected node from the rule. + // It's not pretty, but it avoids an undefined grammar (which is much less pretty!). + auto node = steal<ValueNode>($1); + if (dynamic_cast<FieldValueNode*>(node.get()) == nullptr) { + throw syntax_error(@$, "expected field spec, doctype, bool or comparison"); + } + // Implicit rewrite to non-null comparison node + $$ = new Compare(std::move(node), + FunctionOperator::NE, + std::make_unique<NullValueNode>(), + bucket_id_factory); + } + ; + +logical_expr + : leaf { $$ = $1; } + | logical_expr AND logical_expr { $$ = new And(steal<Node>($1), steal<Node>($3)); } + | logical_expr OR logical_expr { $$ = new Or(steal<Node>($1), steal<Node>($3)); } + | NOT logical_expr %prec UNOT { $$ = new Not(steal<Node>($2)); } + | "(" logical_expr ")" { $$ = $2; $$->setParentheses(); } + ; + +expression + : logical_expr { $$ = $1; } + ; + +entry + : expression END { recv_expr = steal<Node>($1); } + | END { recv_expr = std::make_unique<Constant>(true); } + ; + +%% + +void document::select::DocSelParser::error(const location_type& l, const std::string& what) { + throw syntax_error(l, what); +} + +void throw_parser_syntax_error(const document::select::DocSelParser::location_type& loc, const std::string& msg) { + throw document::select::DocSelParser::syntax_error(loc, msg); +} + +#pragma GCC diagnostic pop diff --git a/document/src/vespa/document/select/node.h b/document/src/vespa/document/select/node.h index eab46e824c8..83e2ea3542d 100644 --- a/document/src/vespa/document/select/node.h +++ b/document/src/vespa/document/select/node.h @@ -5,9 +5,8 @@ * * @brief Base class for all nodes in the document selection tree. * - * @author H�kon Humberset + * @author Håkon Humberset * @date 2005-06-07 - * @version $Id$ */ #pragma once diff --git a/document/src/vespa/document/select/orderingselector.cpp b/document/src/vespa/document/select/orderingselector.cpp index bf8e96c0533..836647aab26 100644 --- a/document/src/vespa/document/select/orderingselector.cpp +++ b/document/src/vespa/document/select/orderingselector.cpp @@ -137,7 +137,6 @@ namespace { void visitArithmeticValueNode(const ArithmeticValueNode &) override {} void visitFunctionValueNode(const FunctionValueNode &) override {} void visitIdValueNode(const IdValueNode &) override {} - void visitSearchColumnValueNode(const SearchColumnValueNode &) override {} void visitFieldValueNode(const FieldValueNode &) override {} void visitFloatValueNode(const FloatValueNode &) override {} void visitVariableValueNode(const VariableValueNode &) override {} diff --git a/document/src/vespa/document/select/parse_utils.cpp b/document/src/vespa/document/select/parse_utils.cpp new file mode 100644 index 00000000000..ab4ce2f6d4a --- /dev/null +++ b/document/src/vespa/document/select/parse_utils.cpp @@ -0,0 +1,37 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "parse_utils.h" +#include <boost/spirit/include/qi.hpp> + +namespace document::select::util { + +namespace qi = boost::spirit::qi; + +// TODO replace use of Spirit.Qi with std::from_string when available. +// Note: these parsers are all pure, reentrant and without locking. +bool parse_hex_i64(const char* str, size_t len, int64_t& out) { + const char* iter = str; + const char* end = str + len; + // Legacy parser parses hex numbers as u64 rather than i64 (then implicitly + // converts), so we do the same thing here to avoid change of semantics. + using u64_hex_parser = qi::uint_parser<uint64_t, 16, 1, 16>; + u64_hex_parser u64_hex; + uint64_t tmp = 0; + const bool ok = qi::parse(iter, end, u64_hex, tmp); + out = static_cast<int64_t>(tmp); + return (ok && (iter == end)); +} +bool parse_i64(const char* str, size_t len, int64_t& out) { + const char* iter = str; + const char* end = str + len; + const bool ok = qi::parse(iter, end, qi::long_long, out); + return (ok && (iter == end)); +} +bool parse_double(const char* str, size_t len, double& out) { + const char* iter = str; + const char* end = str + len; + const bool ok = qi::parse(iter, end, qi::double_, out); + return (ok && (iter == end)); +} + +} diff --git a/document/src/vespa/document/select/parse_utils.h b/document/src/vespa/document/select/parse_utils.h new file mode 100644 index 00000000000..38c36dfe94a --- /dev/null +++ b/document/src/vespa/document/select/parse_utils.h @@ -0,0 +1,17 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <stdint.h> +#include <stddef.h> + +namespace document::select::util { + +// Fast, locale-independent numeric parse helpers for Flex lexing. + +// For all parse_* functions, returns true if parsing is successful. False otherwise. +// Value of `out` is undefined if return value is false. +bool parse_hex_i64(const char* str, size_t len, int64_t& out); +bool parse_i64(const char* str, size_t len, int64_t& out); +bool parse_double(const char* str, size_t len, double& out); + +}
\ No newline at end of file diff --git a/document/src/vespa/document/select/parser.cpp b/document/src/vespa/document/select/parser.cpp index ceaf0b0c438..9f015409011 100644 --- a/document/src/vespa/document/select/parser.cpp +++ b/document/src/vespa/document/select/parser.cpp @@ -1,1493 +1,33 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - #include "parser.h" -#include "branch.h" -#include "compare.h" -#include "constant.h" -#include "operator.h" -#include "doctype.h" -#include "valuenode.h" -#include "simpleparser.h" - -#include <vespa/document/repo/documenttyperepo.h> +#include "scanner.h" #include <vespa/document/base/exceptions.h> #include <vespa/document/util/stringutil.h> -#include <vespa/vespalib/stllike/asciistream.h> -#include <vespa/vespalib/locale/c.h> -#include <boost/spirit/include/classic_chset.hpp> -#include <boost/spirit/include/classic_core.hpp> -#include <boost/spirit/include/classic_escape_char.hpp> -#include <boost/spirit/include/classic_grammar_def.hpp> -#include <boost/spirit/include/classic_parse_tree.hpp> -#include <boost/spirit/include/classic_tree_to_xml.hpp> -#include <iostream> -#include <map> +#include <vespa/vespalib/util/stringfmt.h> #include <sstream> -using boost::spirit::classic::tree_node; -using document::DocumentTypeRepo; -using std::unique_ptr; -using std::cerr; -using std::endl; -using std::istringstream; -using std::ostringstream; -using vespalib::IllegalStateException; - -/* - * This cannot be part of a plugin. boost contains constructs causing - * compiler to generate calls to atexit(). - */ - -#define parse_assert(a) - -namespace document { -namespace select { - -VESPA_IMPLEMENT_EXCEPTION(ParsingFailedException, vespalib::Exception); - -Parser::Parser(const DocumentTypeRepo& repo, - const BucketIdFactory& bucketIdFactory) - : _repo(repo), - _bucketIdFactory(bucketIdFactory) -{ -} - -namespace { - -/** - * Defines the grammar for the document selection text format. - */ -struct DocSelectionGrammar - : public boost::spirit::classic::grammar<DocSelectionGrammar> -{ - /** Node identifiers (value 0 should not be used) */ - enum ids { id_nil=1, id_bool, id_number, id_string, - id_doctype, id_fieldname, id_function, id_idarg, id_searchcolumnarg, - id_operator, id_idspec, id_searchcolumnspec, id_fieldspec, id_value, - id_valuefuncadd, id_valuefuncmul, id_valuefuncmod, - id_valuegroup, id_arithmvalue, - id_comparison, id_leaf, id_not, id_and, - id_or, id_group, id_order, id_expression, id_variable }; - - const DocumentTypeRepo &_repo; - const BucketIdFactory& _bucketIdFactory; - - DocSelectionGrammar(const DocumentTypeRepo& repo, - const BucketIdFactory& bucketIdFactory) - : _repo(repo), - _bucketIdFactory(bucketIdFactory) {} - - const BucketIdFactory& getBucketIdFactory() const - { return _bucketIdFactory; } - - /** Grammar base types. To be able to retrieve different grammars. */ - template <typename Scanner> - struct gram_base { - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_nil> > rule_nil; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_bool> > rule_bool; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_number> > rule_number; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_string> > rule_string; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_doctype> > rule_doctype; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_fieldname> > rule_fieldname; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_function> > rule_function; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_idarg> > rule_idarg; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_searchcolumnarg> > rule_searchcolumnarg; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_operator> > rule_operator; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_idspec> > rule_idspec; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_searchcolumnspec> > rule_searchcolumnspec; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_fieldspec> > rule_fieldspec; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_value> > rule_value; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_valuefuncadd> > rule_valuefuncadd; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_valuefuncmul> > rule_valuefuncmul; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_valuefuncmod> > rule_valuefuncmod; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_valuegroup> > rule_valuegroup; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_arithmvalue> > rule_arithmvalue; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_comparison> > rule_comparison; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_leaf> > rule_leaf; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_not> > rule_not; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_and> > rule_and; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_or> > rule_or; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_group> > rule_group; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_order> > rule_order; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_expression> > rule_expression; - typedef typename boost::spirit::classic::rule<Scanner, - boost::spirit::classic::parser_tag<id_variable> > rule_variable; - typedef boost::spirit::classic::grammar_def<rule_expression, - rule_leaf, - rule_arithmvalue> type; - }; - - template <typename Scanner> - struct definition : gram_base<Scanner>::type - { - typename gram_base<Scanner>::rule_nil _nil; - typename gram_base<Scanner>::rule_bool _bool; - typename gram_base<Scanner>::rule_number _number; - typename gram_base<Scanner>::rule_string _string; - typename gram_base<Scanner>::rule_doctype _doctype; - typename gram_base<Scanner>::rule_fieldname _fieldname; - typename gram_base<Scanner>::rule_function _function; - typename gram_base<Scanner>::rule_idarg _idarg; - typename gram_base<Scanner>::rule_searchcolumnarg _searchcolumnarg; - typename gram_base<Scanner>::rule_operator _operator; - typename gram_base<Scanner>::rule_idspec _idspec; - typename gram_base<Scanner>::rule_searchcolumnspec _searchcolumnspec; - typename gram_base<Scanner>::rule_fieldspec _fieldspec; - typename gram_base<Scanner>::rule_value _value; - typename gram_base<Scanner>::rule_valuefuncadd _valuefuncadd; - typename gram_base<Scanner>::rule_valuefuncmul _valuefuncmul; - typename gram_base<Scanner>::rule_valuefuncmod _valuefuncmod; - typename gram_base<Scanner>::rule_valuegroup _valuegroup; - typename gram_base<Scanner>::rule_arithmvalue _arithmvalue; - typename gram_base<Scanner>::rule_comparison _comparison; - typename gram_base<Scanner>::rule_leaf _leaf; - typename gram_base<Scanner>::rule_not _not; - typename gram_base<Scanner>::rule_and _and; - typename gram_base<Scanner>::rule_or _or; - typename gram_base<Scanner>::rule_group _group; - typename gram_base<Scanner>::rule_order _order; - typename gram_base<Scanner>::rule_expression _expression; - typename gram_base<Scanner>::rule_variable _variable; +namespace document::select { - definition(const DocSelectionGrammar&) - : _nil(), - _bool(), - _number(), - _string(), - _doctype(), - _fieldname(), - _function(), - _idarg(), - _operator(), - _idspec(), - _searchcolumnspec(), - _fieldspec(), - _value(), - _valuefuncadd(), - _valuefuncmul(), - _valuefuncmod(), - _valuegroup(), - _arithmvalue(), - _comparison(), - _leaf(), - _not(), - _and(), - _or(), - _group(), - _order(), - _expression(), - _variable() - { - using namespace boost::spirit::classic; +std::unique_ptr<Node> Parser::parse(const std::string& str) const { + try { + std::istringstream ss(str); + DocSelScanner scanner(&ss); - boost::spirit::classic::uint_parser<uint64_t, 16, 1, -1> hexvalue; - - // Initialize primitives - _nil = lexeme_d[ as_lower_d["null"] ]; - _bool = lexeme_d[ as_lower_d["true"] | as_lower_d["false"] ]; - _number = lexeme_d[ str_p("0x") >> hexvalue ] | lexeme_d[ real_p ]; - _string = ( lexeme_d[ - ( no_node_d[ ch_p('"') ] >> - token_node_d[ *( ~chset<>("\\\"\x00-\x1f\x7f-\xff") | - ( '\\' >> ( ch_p('\\') | 't' | 'n' | 'f' | 'r' | '"' | - (ch_p('x') >> xdigit_p >> xdigit_p) ) ) ) ] >> - no_node_d[ ch_p('"') ] ) | - ( no_node_d[ ch_p('\'') ] >> - token_node_d[ *( ~chset<>("\\'\x00-\x1f\x7f-\xff") | - ( '\\' >> ( ch_p('\\') | 't' | 'n' | 'f' | 'r' | '\'' | - (ch_p('x') >> xdigit_p >> xdigit_p) ) ) ) ] >> - no_node_d[ ch_p('\'') ] ) - ] ); - _doctype = lexeme_d[ token_node_d[ chset<>("_A-Za-z") - >> *(chset<>("_A-Za-z0-9")) ]]; - _fieldname = lexeme_d[ token_node_d[chset<>("_A-Za-z") - >> *(chset<>("_A-Za-z0-9{}[]$")) - ]]; - _function = lexeme_d[ token_node_d[ chset<>("A-Za-z") - >> *(chset<>("A-Za-z0-9")) ] - >> no_node_d[ str_p("()") ] ]; - - _order = as_lower_d["order"] - >> no_node_d[ ch_p('(') ] - >> _number - >> no_node_d[ ch_p(',') ] - >> _number - >> no_node_d[ ch_p(')') ]; - - _idarg = (as_lower_d[ "scheme"] | as_lower_d[ "namespace"] | - as_lower_d[ "specific" ] | as_lower_d[ "user" ] | - as_lower_d[ "group" ] | as_lower_d[ "bucket" ] | - as_lower_d[ "gid" ] | as_lower_d["type"] | _order); - - _searchcolumnarg = lexeme_d[ token_node_d[ *(chset<>("_A-Za-z0-9")) ]]; - _operator = (str_p(">=") | ">" | "==" | "=~" | "=" - | "<=" | "<" | "!="); - // Derived - _idspec = as_lower_d["id"] - >> !(no_node_d[ ch_p('.') ] >> _idarg); - _searchcolumnspec = as_lower_d["searchcolumn"] - >> !(no_node_d[ ch_p('.') ] >> _searchcolumnarg); - _fieldspec = _doctype - >> +( no_node_d[ ch_p('.') ] >> (_function | _fieldname)); - _variable = lexeme_d[ token_node_d[chset<>("$") - >> *(chset<>("A-Za-z0-9")) - ]]; - _value = (_valuegroup | _function | _nil | _number | _string - | _idspec | _searchcolumnspec | _fieldspec | _variable) - >> *(no_node_d[ ch_p('.') ] >> _function); - _valuefuncmod = (_valuegroup | _value) - >> +( ch_p('%') - >> (_valuegroup | _value) ); - _valuefuncmul = (_valuefuncmod | _valuegroup | _value) - >> +( (ch_p('*') | ch_p('/')) - >> (_valuefuncmod | _valuegroup | _value)); - _valuefuncadd - = (_valuefuncmul | _valuefuncmod | _valuegroup | _value) - >> +((ch_p('+') | ch_p('-')) - >> (_valuefuncmul | _valuefuncmod | _valuegroup | - _value)); - _valuegroup = no_node_d[ ch_p('(') ] >> _arithmvalue - >> no_node_d[ ch_p(')') ] - >> *(no_node_d[ ch_p('.') ] >> _function); - _arithmvalue = (_valuefuncadd | _valuefuncmul | _valuefuncmod - | _valuegroup | _value); - _comparison = _arithmvalue >> _operator >> _arithmvalue; - _leaf = _bool | _comparison | _fieldspec | _doctype; - - _not = (as_lower_d["not"] >> _group) - | (lexeme_d[ as_lower_d["not"] >> no_node_d[ space_p ] ] >> _leaf); - _and = (_not | _group | _leaf) - >> as_lower_d["and"] >> (_and | _not | _group | _leaf); - _or = (_and | _not | _group | _leaf) - >> as_lower_d["or"] >> (_or | _and | _not | _group | _leaf); - _group = no_node_d[ ch_p('(') ] - >> (_or | _and | _not | _group | _leaf) - >> no_node_d[ ch_p(')') ]; - - _expression = !(_or | _and | _not | _group | _leaf) >> end_p; - - this->start_parsers(_expression, _leaf, _arithmvalue); + std::unique_ptr<Node> root; + DocSelParser parser(scanner, _bucket_id_factory, _doc_type_repo, root); + if (parser.parse() != 0) { + throw ParsingFailedException( + vespalib::make_string("Unknown parse failure while parsing selection '%s'", str.c_str()), + VESPA_STRLOC); } - }; - -}; - -template<typename T> -std::unique_ptr<Node> -parseTree(DocSelectionGrammar& grammar, tree_node<T>& root) { - return parseNode(grammar, root); -} - -template<typename T> -std::unique_ptr<Node> -parseNode(DocSelectionGrammar& grammar, tree_node<T>& node) { - switch (node.value.id().to_long()) { - case DocSelectionGrammar::id_or: - return parseOr(grammar, node); - case DocSelectionGrammar::id_and: - return parseAnd(grammar, node); - case DocSelectionGrammar::id_not: - return parseNot(grammar, node); - case DocSelectionGrammar::id_group: - { - std::unique_ptr<Node> n(parseNode(grammar, node.children[0])); - n->setParentheses(); - return n; - } - case DocSelectionGrammar::id_leaf: - case DocSelectionGrammar::id_value: - parse_assert(node.children.size() == 1); - return parseNode(grammar, node.children[0]); - case DocSelectionGrammar::id_expression: - if (node.children.size() == 1) { - return parseNode(grammar, node.children[0]); - } - parse_assert(node.children.size() == 0); - return std::unique_ptr<Node>(new Constant("true")); - case DocSelectionGrammar::id_bool: - return parseBool(grammar, node); - case DocSelectionGrammar::id_comparison: - return parseComparison(grammar, node); - case DocSelectionGrammar::id_fieldspec: - return parseFieldSpec(grammar, node); - case DocSelectionGrammar::id_doctype: - return parseDocType(grammar, node); - } - vespalib::asciistream ost; - ost << "Received unhandled nodetype " - << node.value.id().to_long() << " in parseNode()\n"; - throw IllegalStateException(ost.str(), VESPA_STRLOC); -} - -template<typename T> -std::unique_ptr<Node> -parseOr(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_or); - parse_assert(node.children.size() == 3); - vespalib::string op(node.children[1].value.begin(), - node.children[1].value.end()); - return std::unique_ptr<Node>(new Or( - parseNode(grammar, node.children[0]), - parseNode(grammar, node.children[2]), - op.c_str())); -} - -template<typename T> -std::unique_ptr<Node> -parseAnd(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_and); - parse_assert(node.children.size() == 3); - vespalib::string op(node.children[1].value.begin(), - node.children[1].value.end()); - return std::unique_ptr<Node>(new And( - parseNode(grammar, node.children[0]), - parseNode(grammar, node.children[2]), - op.c_str())); -} - -template<typename T> -std::unique_ptr<Node> -parseNot(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_not); - parse_assert(node.children.size() == 2); - vespalib::string op(node.children[0].value.begin(), - node.children[0].value.end()); - return std::unique_ptr<Node>(new Not( - parseNode(grammar, node.children[1]), op.c_str())); -} - -template<typename T> -std::unique_ptr<Node> -parseBool(DocSelectionGrammar& grammar, tree_node<T>& node) { - (void) grammar; - parse_assert(node.value.id().to_long() == grammar.id_bool); - parse_assert(node.children.size() == 1); - parse_assert(node.children[0].value.id().to_long() == grammar.id_bool); - parse_assert(node.children[0].children.size() == 0); - vespalib::string s(node.children[0].value.begin(), node.children[0].value.end()); - return std::unique_ptr<Node>(new Constant(s)); -} - -template<typename T> -std::unique_ptr<Node> -parseComparison(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_comparison); - parse_assert(node.children.size() == 3); - parse_assert(node.children[1].children.size() == 1); - vespalib::string op(node.children[1].children[0].value.begin(), - node.children[1].children[0].value.end()); - return std::unique_ptr<Node>(new Compare( - parseArithmValue(grammar, node.children[0]), - Operator::get(op), - parseArithmValue(grammar, node.children[2]), - grammar.getBucketIdFactory())); -} - -template<typename T> -std::unique_ptr<Node> -parseFieldSpec(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_fieldspec); - return std::unique_ptr<Node>(new Compare( - parseFieldSpecValue(grammar, node), - Operator::get("!="), - std::unique_ptr<ValueNode>(new NullValueNode("null")), - grammar.getBucketIdFactory())); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseVariable(DocSelectionGrammar& grammar, tree_node<T>& node) { - (void) grammar; - parse_assert(node.value.id().to_long() == grammar.id_variable); - vespalib::string varName(node.children[0].value.begin(), - node.children[0].value.end()); - return std::unique_ptr<ValueNode>(new VariableValueNode(varName.substr(1))); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseGlobValueFunction(DocSelectionGrammar& grammar, tree_node<T>& node) { - (void) grammar; - parse_assert(node.value.id().to_long() == grammar.id_function); - vespalib::string varName(node.children[0].value.begin(), - node.children[0].value.end()); - if (varName == "now") { - return std::unique_ptr<ValueNode>(new CurrentTimeValueNode); + return root; + } catch (const DocSelParser::syntax_error& err) { + throw ParsingFailedException( + vespalib::make_string("%s at column %u when parsing selection '%s'", + err.what(), err.location.begin.column, str.c_str()), + VESPA_STRLOC); } - throw ParsingFailedException("Unexpected function name '" + varName - + "' found.", VESPA_STRLOC); } -template<typename T> -std::unique_ptr<Node> -parseDocType(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_doctype); - parse_assert(node.children.size() == 1); - parse_assert(node.children[0].value.id().to_long() == grammar.id_doctype); - parse_assert(node.children[0].children.size() == 0); - vespalib::string doctype(node.children[0].value.begin(), - node.children[0].value.end()); - // Verify existance of any version of document - if (!grammar._repo.getDocumentType(doctype)) { - throw ParsingFailedException("Document type " + doctype + " not found", - VESPA_STRLOC); - } - return std::unique_ptr<Node>(new DocType(doctype)); -} - -template<typename T> -std::unique_ptr<ValueNode> -addFunctions(DocSelectionGrammar& grammar, tree_node<T>& node, - std::unique_ptr<ValueNode> src, uint32_t index) -{ - (void) grammar; - while (index < node.children.size()) { - parse_assert(node.children[index].value.id().to_long() - == grammar.id_function); - vespalib::string func(node.children[index].children[0].value.begin(), - node.children[index].children[0].value.end()); - std::unique_ptr<ValueNode> fnode(new FunctionValueNode(func, std::move(src))); - src = std::move(fnode); - ++index; - } - return std::move(src); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseArithmValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - switch (node.value.id().to_long()) { - case DocSelectionGrammar::id_arithmvalue: - parse_assert(node.children.size() == 1); - return parseArithmValue(grammar, node.children[0]); - case DocSelectionGrammar::id_value: - return parseValue(grammar, node); - case DocSelectionGrammar::id_valuegroup: - return parseValueGroup(grammar, node); - case DocSelectionGrammar::id_valuefuncadd: - case DocSelectionGrammar::id_valuefuncmul: - case DocSelectionGrammar::id_valuefuncmod: - return parseValueArithmetics(grammar, node); - } - vespalib::asciistream ost; - ost << "Received unhandled nodetype " - << node.value.id().to_long() - << " in parseArithmValue()\n"; - throw IllegalStateException(ost.str(), VESPA_STRLOC); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseValueArithmetics(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.children.size() >= 3 && node.children.size() % 2 == 1); - std::unique_ptr<ValueNode> lhs(parseArithmValue(grammar, node.children[0])); - for (unsigned int i = 1; i < node.children.size(); i += 2) { - vespalib::string op(node.children[i].value.begin(), - node.children[i].value.end()); - std::unique_ptr<ValueNode> rhs(parseArithmValue(grammar, - node.children[i + 1])); - std::unique_ptr<ValueNode> res( - new ArithmeticValueNode(std::move(lhs), op, std::move(rhs))); - lhs = std::move(res); - } - return lhs; -} - -template<typename T> -std::unique_ptr<ValueNode> -parseValueGroup(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_valuegroup); - parse_assert(node.children.size() >= 1); - std::unique_ptr<ValueNode> result( - parseArithmValue(grammar, node.children[0])); - result->setParentheses(); - return addFunctions(grammar, node, std::move(result), 1); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_value); - parse_assert(node.children.size() >= 1); - std::unique_ptr<ValueNode> result; - switch (node.children[0].value.id().to_long()) { - case DocSelectionGrammar::id_nil: - result = parseNilValue(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_idspec: - result = parseIdSpecValue(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_searchcolumnspec: - result = parseSearchColumnSpecValue(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_fieldspec: - result = parseFieldSpecValue(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_number: - result = parseNumberValue(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_string: - result = parseStringValue(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_valuegroup: - result = parseValueGroup(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_variable: - result = parseVariable(grammar, node.children[0]); - break; - case DocSelectionGrammar::id_function: - result = parseGlobValueFunction(grammar, node.children[0]); - break; - default: - vespalib::asciistream ost; - ost << "Received unhandled nodetype " - << node.children[0].value.id().to_long() - << " in parseValue(), from node of type " - << node.value.id().to_long() << "\n"; - throw IllegalStateException(ost.str(), VESPA_STRLOC); - } - return addFunctions(grammar, node, std::move(result), 1); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseNilValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - (void) grammar; - parse_assert(node.value.id().to_long() == grammar.id_nil); - parse_assert(node.children.size() == 1); - parse_assert(node.children[0].children.size() == 0); - vespalib::string op(node.children[0].value.begin(), - node.children[0].value.end()); - return std::unique_ptr<ValueNode>(new NullValueNode(op)); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseIdSpecValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_idspec); - parse_assert(node.children.size() >= 1); - parse_assert(node.children[0].children.size() == 0); - vespalib::string id(node.children[0].value.begin(), - node.children[0].value.end()); - if (node.children.size() == 1) { - return std::unique_ptr<ValueNode>( - new IdValueNode(grammar.getBucketIdFactory(), id, "")); - } - - vespalib::string type; - - int widthBits = -1; - int divisionBits = -1; - - if (node.children[1].children[0].value.id().to_long() == grammar.id_order) { - tree_node<T>& ordernode(node.children[1].children[0]); - type = vespalib::string(ordernode.children[0].value.begin(), - ordernode.children[0].value.end()); - - vespalib::string val = vespalib::string( - ordernode.children[1].children[0].value.begin(), - ordernode.children[1].children[0].value.end()); - widthBits = atoi(val.c_str()); - - val = vespalib::string(ordernode.children[2].children[0].value.begin(), - ordernode.children[2].children[0].value.end()); - divisionBits = atoi(val.c_str()); - } else { - type = vespalib::string(node.children[1].children[0].value.begin(), - node.children[1].children[0].value.end()); - } - - return std::unique_ptr<ValueNode>( - new IdValueNode(grammar.getBucketIdFactory(), id, type, - widthBits, divisionBits)); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseSearchColumnSpecValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_searchcolumnspec); - parse_assert(node.children.size() == 2); - parse_assert(node.children[0].children.size() == 0); - parse_assert(node.children[1].value.id().to_long() == grammar.id_searchcolumnarg); - - vespalib::string id(node.children[0].value.begin(), - node.children[0].value.end()); - parse_assert(node.children.size() == 2); - - vespalib::string val = vespalib::string(node.children[1].children[0].value.begin(), - node.children[1].children[0].value.end()); - return std::unique_ptr<ValueNode>(new SearchColumnValueNode( - grammar.getBucketIdFactory(), id, atoi(val.c_str()))); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseFieldSpecValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - parse_assert(node.value.id().to_long() == grammar.id_fieldspec); - parse_assert(node.children.size() >= 2); - parse_assert(node.children[0].value.id().to_long() == grammar.id_doctype); - vespalib::string doctype(node.children[0].children[0].value.begin(), - node.children[0].children[0].value.end()); - // Verify that document type exist at any version - if (!grammar._repo.getDocumentType(doctype)) { - throw ParsingFailedException("Document type " + doctype + " not found", - VESPA_STRLOC); - } - std::unique_ptr<ValueNode> value; - uint32_t iterator = 2; - - parse_assert(node.children[1].value.id().to_long() == grammar.id_fieldname); - vespalib::string field(node.children[1].children[0].value.begin(), - node.children[1].children[0].value.end()); - while (iterator < node.children.size() - && node.children[iterator].value.id().to_long() == grammar.id_fieldname) - { - field += "." + vespalib::string( - node.children[iterator].children[0].value.begin(), - node.children[iterator].children[0].value.end()); - ++iterator; - } - value.reset(new FieldValueNode(doctype, field)); - - for (; iterator<node.children.size(); ++iterator) { - std::unique_ptr<ValueNode> child(std::move(value)); - vespalib::string function(node.children[iterator].children[0].value.begin(), - node.children[iterator].children[0].value.end()); - parse_assert(node.children[iterator].value.id().to_long() == grammar.id_function); - value.reset(new FunctionValueNode(function, std::move(child))); - } - return value; -} - -template<typename T> -std::unique_ptr<ValueNode> -parseNumberValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - (void) grammar; - parse_assert(node.value.id().to_long() == grammar.id_number); - vespalib::string sval; - int base = 10; - if (node.children.size() == 2) { - base = 16; - sval = vespalib::string(node.children[1].value.begin(), - node.children[1].value.end()); - parse_assert(node.children[0].value.id().to_long() == grammar.id_number); - parse_assert(node.children[1].value.id().to_long() == grammar.id_number); - } else { - parse_assert(node.children.size() == 1); - sval = vespalib::string(node.children[0].value.begin(), - node.children[0].value.end()); - parse_assert(node.children[0].value.id().to_long() == grammar.id_number); - } - if (sval.find('.') != vespalib::string::npos) { - char* endptr; - double val = vespalib::locale::c::strtod(sval.c_str(), &endptr); - if (*endptr == '\0') { - return std::unique_ptr<ValueNode>(new FloatValueNode(val)); - } - } else { - char* endptr; - int64_t val; - if (base == 16) { - val = strtoull(sval.c_str(), &endptr, base); - } else { - val = strtoll(sval.c_str(), &endptr, base); - } - if (*endptr == '\0') { - return std::unique_ptr<ValueNode>(new IntegerValueNode(val, false)); - } - } - vespalib::string error = "'" + sval + "' is not a valid number."; - throw ParsingFailedException(error, VESPA_STRLOC); -} - -template<typename T> -std::unique_ptr<ValueNode> -parseStringValue(DocSelectionGrammar& grammar, tree_node<T>& node) { - (void) grammar; - parse_assert(node.value.id().to_long() == grammar.id_string); - if (node.children.size() == 0) { - return std::unique_ptr<ValueNode>(new StringValueNode("")); - } - parse_assert(node.children.size() == 1); - parse_assert(node.children[0].value.id().to_long() == grammar.id_string); - vespalib::string val(node.children[0].value.begin(), - node.children[0].value.end()); - return std::unique_ptr<ValueNode>(new StringValueNode(StringUtil::unescape(val))); -} - -template<typename Tree> -void printSpiritTree(std::ostream& out, Tree tree, const vespalib::string& query, - const DocSelectionGrammar& grammar) { - using boost::spirit::classic::parser_id; - - std::map<parser_id, vespalib::string> names; - names[parser_id(grammar.id_bool)] = "bool"; - names[parser_id(grammar.id_number)] = "number"; - names[parser_id(grammar.id_string)] = "string"; - names[parser_id(grammar.id_doctype)] = "doctype"; - names[parser_id(grammar.id_fieldname)] = "fieldname"; - names[parser_id(grammar.id_function)] = "function"; - names[parser_id(grammar.id_idarg)] = "idarg"; - names[parser_id(grammar.id_searchcolumnarg)] = "searchcolumnarg"; - names[parser_id(grammar.id_operator)] = "operator"; - names[parser_id(grammar.id_idspec)] = "idspec"; - names[parser_id(grammar.id_searchcolumnspec)] = "searchcolumnspec"; - names[parser_id(grammar.id_fieldspec)] = "fieldspec"; - names[parser_id(grammar.id_value)] = "value"; - names[parser_id(grammar.id_valuefuncadd)] = "valuefuncadd"; - names[parser_id(grammar.id_valuefuncmul)] = "valuefuncmul"; - names[parser_id(grammar.id_valuefuncmod)] = "valuefuncmod"; - names[parser_id(grammar.id_valuegroup)] = "valuegroup"; - names[parser_id(grammar.id_arithmvalue)] = "arithmvalue"; - names[parser_id(grammar.id_comparison)] = "comparison"; - names[parser_id(grammar.id_leaf)] = "leaf"; - names[parser_id(grammar.id_not)] = "not"; - names[parser_id(grammar.id_and)] = "and"; - names[parser_id(grammar.id_or)] = "or"; - names[parser_id(grammar.id_group)] = "group"; - names[parser_id(grammar.id_expression)] = "expression"; - tree_to_xml(out, tree, query.c_str(), names); -} - -template<typename Parser> -bool testExpr(const DocumentTypeRepo& repo, - const BucketIdFactory& factory, - const vespalib::string& expression, const Parser& parser, - const vespalib::string& result) -{ - //std::cerr << "Testing expression '" << expression << "'.\n"; - using boost::spirit::classic::space_p; - - DocSelectionGrammar grammar(repo, factory); - boost::spirit::classic::tree_parse_info<> info; - info = pt_parse(expression.c_str(), parser, - space_p); - std::ostringstream ost; - printSpiritTree(ost, info.trees, expression, grammar); - if (!info.full) { - cerr << "Expression '" << expression - << "' wasn't completely parsed\n" - << ost.str() << "\n"; - return false; - } - vespalib::string httpexpr = expression; - vespalib::string::size_type index; - while ((index = httpexpr.find('>')) != vespalib::string::npos) { - httpexpr = httpexpr.substr(0,index) + ">" - + httpexpr.substr(index+1); - } - vespalib::string fullresult = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" - "<!DOCTYPE parsetree SYSTEM \"parsetree.dtd\">\n" - "<!-- " + httpexpr + " -->\n" + result; - //if (ost.str() != fullresult) { - if (fullresult != ost.str()) { - cerr << "Parsing expression '" << expression << "', expected\n" - << fullresult << "\nbut got\n" << ost.str() << "\n"; - return false; - } - return true; -} - -bool test(const DocumentTypeRepo& repo, - const BucketIdFactory& bucketIdFactory) -{ - //std::cerr << "\n\nTESTING DOCUMENT SELECT PARSER\n\n"; - DocSelectionGrammar grammar(repo, bucketIdFactory); - - using boost::spirit::classic::space_p; - - // Parser two is the arithmvalue.. - // idspec, fieldspec, number & stringval, + - * / % () - testExpr(repo, bucketIdFactory, "3.14", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>3.14</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "-999", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>-999</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "15e4", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>15e4</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "3.4e-4", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>3.4e-4</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "\" Test \"", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"string\">\n" - " <parsenode rule=\"string\">\n" - " <value> Test </value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "id", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"idspec\">\n" - " <parsenode rule=\"idspec\">\n" - " <value>id</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "id.namespace", - grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"idspec\">\n" - " <parsenode rule=\"idspec\">\n" - " <value>id</value>\n" - " </parsenode>\n" - " <parsenode rule=\"idarg\">\n" - " <parsenode rule=\"idarg\">\n" - " <value>namespace</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "id.hash()", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"idspec\">\n" - " <parsenode rule=\"idspec\">\n" - " <value>id</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"function\">\n" - " <parsenode rule=\"function\">\n" - " <value>hash</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "id.namespace.hash()", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"idspec\">\n" - " <parsenode rule=\"idspec\">\n" - " <value>id</value>\n" - " </parsenode>\n" - " <parsenode rule=\"idarg\">\n" - " <parsenode rule=\"idarg\">\n" - " <value>namespace</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"function\">\n" - " <parsenode rule=\"function\">\n" - " <value>hash</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "music.artist", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"fieldspec\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>music</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"fieldname\">\n" - " <parsenode rule=\"fieldname\">\n" - " <value>artist</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "music.artist.lowercase()", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"fieldspec\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>music</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"fieldname\">\n" - " <parsenode rule=\"fieldname\">\n" - " <value>artist</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"function\">\n" - " <parsenode rule=\"function\">\n" - " <value>lowercase</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "(43)", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"valuegroup\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>43</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "1 + 2 * 3 - 10 % 2 / 3", grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>1</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <value>+</value>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>2</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <value>*</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>3</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <value>-</value>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <parsenode rule=\"valuefuncmod\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>10</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmod\">\n" - " <value>%</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>2</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <value>/</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>3</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "(43 + 14) / 34", - grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <parsenode rule=\"valuegroup\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>43</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <value>+</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>14</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <value>/</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>34</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "34 * (3 - 1) % 4", - grammar.use_parser<2>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>34</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmul\">\n" - " <value>*</value>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmod\">\n" - " <parsenode rule=\"valuegroup\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>3</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncadd\">\n" - " <value>-</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>1</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"valuefuncmod\">\n" - " <value>%</value>\n" - " </parsenode>\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>4</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - - // Parser 1 is a leaf. bool, comparison, fieldspec, doctype - testExpr(repo, bucketIdFactory, "true", grammar.use_parser<1>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>true</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "false", grammar.use_parser<1>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>false</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "music.test", grammar.use_parser<1>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"fieldspec\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>music</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"fieldname\">\n" - " <parsenode rule=\"fieldname\">\n" - " <value>test</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, "music", grammar.use_parser<1>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>music</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "music.artist = \"*john*\"", grammar.use_parser<1>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"comparison\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"fieldspec\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>music</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"fieldname\">\n" - " <parsenode rule=\"fieldname\">\n" - " <value>artist</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"operator\">\n" - " <parsenode rule=\"operator\">\n" - " <value>=</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"string\">\n" - " <parsenode rule=\"string\">\n" - " <value>*john*</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "music.length >= 180", grammar.use_parser<1>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"comparison\">\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"fieldspec\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>music</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"fieldname\">\n" - " <parsenode rule=\"fieldname\">\n" - " <value>length</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"operator\">\n" - " <parsenode rule=\"operator\">\n" - " <value>>=</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"arithmvalue\">\n" - " <parsenode rule=\"value\">\n" - " <parsenode rule=\"number\">\n" - " <parsenode rule=\"number\">\n" - " <value>180</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - - // Parser 0 - The whole expression - testExpr(repo, bucketIdFactory, - "true oR nOt false And true", grammar.use_parser<0>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"expression\">\n" - " <parsenode rule=\"or\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>true</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"or\">\n" - " <value>oR</value>\n" - " </parsenode>\n" - " <parsenode rule=\"and\">\n" - " <parsenode rule=\"not\">\n" - " <parsenode rule=\"not\">\n" - " <value>nOt</value>\n" - " </parsenode>\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>false</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"and\">\n" - " <value>And</value>\n" - " </parsenode>\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>true</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "(true oR false) aNd true", grammar.use_parser<0>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"expression\">\n" - " <parsenode rule=\"and\">\n" - " <parsenode rule=\"group\">\n" - " <parsenode rule=\"or\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>true</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"or\">\n" - " <value>oR</value>\n" - " </parsenode>\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>false</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"and\">\n" - " <value>aNd</value>\n" - " </parsenode>\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"bool\">\n" - " <parsenode rule=\"bool\">\n" - " <value>true</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - testExpr(repo, bucketIdFactory, - "iddoc or not(notand and ornot)", grammar.use_parser<0>(), - "<parsetree version=\"1.0\">\n" - " <parsenode rule=\"expression\">\n" - " <parsenode rule=\"or\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>iddoc</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"or\">\n" - " <value>or</value>\n" - " </parsenode>\n" - " <parsenode rule=\"not\">\n" - " <parsenode rule=\"not\">\n" - " <value>not</value>\n" - " </parsenode>\n" - " <parsenode rule=\"group\">\n" - " <parsenode rule=\"and\">\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>notand</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " <parsenode rule=\"and\">\n" - " <value>and</value>\n" - " </parsenode>\n" - " <parsenode rule=\"leaf\">\n" - " <parsenode rule=\"doctype\">\n" - " <parsenode rule=\"doctype\">\n" - " <value>ornot</value>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - " </parsenode>\n" - "</parsetree>\n"); - return true; -} - -} - -vespalib::Lock Parser::_G_parseLock; - -unique_ptr<Node> Parser::parse(const vespalib::stringref & s) -{ - - simple::SelectionParser simple(_bucketIdFactory); - if (simple.parse(s) && simple.getRemaining().empty()) { - Node::UP tmp(simple.getNode()); - assert(tmp.get() != NULL); - return tmp; - } else { - return fullParse(s); - } -} - -unique_ptr<Node> Parser::fullParse(const vespalib::stringref & s) -{ - static bool haveTested = test(_repo, _bucketIdFactory); if (haveTested) {} - try{ - vespalib::LockGuard guard(_G_parseLock); - DocSelectionGrammar grammar(_repo, _bucketIdFactory); - boost::spirit::classic::tree_parse_info<> info - = pt_parse(&s[0], &s[0]+s.size(), - grammar.use_parser<0>(), boost::spirit::classic::space_p); - if (!info.full) { - vespalib::string unexpected(info.stop); - unsigned int position = s.size() - unexpected.size(); - if (unexpected.size() > 10) { - unexpected = unexpected.substr(0,10); - } - vespalib::asciistream ost; - ost << "Unexpected token at position " << position << " ('" - << unexpected << "') in query '" << s << "',"; - throw ParsingFailedException(ost.str(), VESPA_STRLOC); - } - parse_assert(info.trees.size() == 1); - //printSpiritTree(std::cerr, info.trees, s, grammar); - return parseTree(grammar, info.trees[0]); - } catch (ParsingFailedException& e) { - throw; - } catch (vespalib::Exception& e) { - throw ParsingFailedException("Parsing failed. See cause exception.", - e, VESPA_STRLOC); - } catch (std::exception& e) { - cerr << "Parser::parse() internal error: " - << e.what() << endl; - throw; // Program will abort when this tries to go out.. - } - return unique_ptr<Node>(); } -} // select -} // document diff --git a/document/src/vespa/document/select/parser.h b/document/src/vespa/document/select/parser.h index 4df00d64bf3..35d710298c3 100644 --- a/document/src/vespa/document/select/parser.h +++ b/document/src/vespa/document/select/parser.h @@ -1,37 +1,40 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - #pragma once #include "node.h" +#include "parsing_failed_exception.h" #include <vespa/document/bucket/bucketidfactory.h> -#include <vespa/vespalib/util/exception.h> -#include <vespa/vespalib/util/sync.h> - -namespace document { -class DocumentTypeRepo; - -namespace select { - -VESPA_DEFINE_EXCEPTION(ParsingFailedException, vespalib::Exception); - +#include <vespa/document/repo/documenttyperepo.h> +#include <memory> +#include <string> + +namespace document::select { + +/** + * Document selection parser built around Flex/Bison. O(n) on input size + * and non-locking. + * + * Thread safety: same as a std::vector + */ class Parser { + const DocumentTypeRepo&_doc_type_repo; + const BucketIdFactory& _bucket_id_factory; public: - Parser(const DocumentTypeRepo&, const BucketIdFactory& bucketIdFactory); + Parser(const DocumentTypeRepo& repo, const BucketIdFactory& bucket_id_factory) + : _doc_type_repo(repo), + _bucket_id_factory(bucket_id_factory) + {} /** * Returns a newly allocated AST root node representing the selection * if parsing is successful. Otherwise, ParsingFailedException will be * thrown. + * + * Thread safe, assuming referenced DocumentTypeRepo and BucketIdFactory + * instances are immutable. */ - std::unique_ptr<Node> parse(const vespalib::stringref& s); - -private: - std::unique_ptr<Node> fullParse(const vespalib::stringref& s); - static vespalib::Lock _G_parseLock; - const DocumentTypeRepo& _repo; - const BucketIdFactory& _bucketIdFactory; + std::unique_ptr<Node> parse(const std::string& str) const; }; -} // select -} // parser +} diff --git a/document/src/vespa/document/select/parsing_failed_exception.cpp b/document/src/vespa/document/select/parsing_failed_exception.cpp new file mode 100644 index 00000000000..ce02389ed46 --- /dev/null +++ b/document/src/vespa/document/select/parsing_failed_exception.cpp @@ -0,0 +1,9 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "parsing_failed_exception.h" +#include <vespa/document/base/exceptions.h> + +namespace document::select { + +VESPA_IMPLEMENT_EXCEPTION(ParsingFailedException, vespalib::Exception); + +}
\ No newline at end of file diff --git a/document/src/vespa/document/select/parsing_failed_exception.h b/document/src/vespa/document/select/parsing_failed_exception.h new file mode 100644 index 00000000000..54138a492e8 --- /dev/null +++ b/document/src/vespa/document/select/parsing_failed_exception.h @@ -0,0 +1,10 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/util/exception.h> + +namespace document::select { + +VESPA_DEFINE_EXCEPTION(ParsingFailedException, vespalib::Exception); + +} diff --git a/document/src/vespa/document/select/scanner.h b/document/src/vespa/document/select/scanner.h new file mode 100644 index 00000000000..5aa9ea1c8d3 --- /dev/null +++ b/document/src/vespa/document/select/scanner.h @@ -0,0 +1,21 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#if !defined(yyFlexLexerOnce) +# include <FlexLexer.h> +#endif + +#include "parser.hxx" +#include "location.hh" +#include <iosfwd> + +namespace document::select { + +class DocSelScanner final : yyFlexLexer { +public: + explicit DocSelScanner(std::istream* in) : yyFlexLexer(in) {} + ~DocSelScanner() override = default; + int yylex(DocSelParser::semantic_type* yylval, DocSelParser::location_type* yyloc); +}; + +} diff --git a/document/src/vespa/document/select/traversingvisitor.cpp b/document/src/vespa/document/select/traversingvisitor.cpp index b8f34540b29..26de6093ddf 100644 --- a/document/src/vespa/document/select/traversingvisitor.cpp +++ b/document/src/vespa/document/select/traversingvisitor.cpp @@ -73,12 +73,6 @@ TraversingVisitor::visitIdValueNode(const IdValueNode &) void -TraversingVisitor::visitSearchColumnValueNode(const SearchColumnValueNode &) -{ -} - - -void TraversingVisitor::visitFieldValueNode(const FieldValueNode &) { } diff --git a/document/src/vespa/document/select/traversingvisitor.h b/document/src/vespa/document/select/traversingvisitor.h index 43d10cfcaa2..f8b0377b102 100644 --- a/document/src/vespa/document/select/traversingvisitor.h +++ b/document/src/vespa/document/select/traversingvisitor.h @@ -21,7 +21,6 @@ public: void visitInvalidConstant(const InvalidConstant &) override; void visitDocumentType(const DocType &) override; void visitIdValueNode(const IdValueNode &) override; - void visitSearchColumnValueNode(const SearchColumnValueNode &) override; void visitFieldValueNode(const FieldValueNode &) override; void visitFloatValueNode(const FloatValueNode &) override; void visitVariableValueNode(const VariableValueNode &) override; diff --git a/document/src/vespa/document/select/valuenodes.cpp b/document/src/vespa/document/select/valuenodes.cpp index 479896f9124..837ebd873e3 100644 --- a/document/src/vespa/document/select/valuenodes.cpp +++ b/document/src/vespa/document/select/valuenodes.cpp @@ -15,7 +15,6 @@ #include <iomanip> #include <sys/time.h> - #include <vespa/log/log.h> LOG_SETUP(".document.select.valuenode"); @@ -61,10 +60,7 @@ InvalidValueNode::print(std::ostream& out, bool verbose, if (hadParentheses()) out << ')'; } -NullValueNode::NullValueNode(const vespalib::stringref & name) - : _name(name) -{ } - +NullValueNode::NullValueNode() {} void NullValueNode::visit(Visitor &visitor) const @@ -79,7 +75,7 @@ NullValueNode::print(std::ostream& out, bool verbose, { (void) verbose; (void) indent; if (hadParentheses()) out << '('; - out << _name; + out << "null"; if (hadParentheses()) out << ')'; } @@ -678,88 +674,6 @@ IdValueNode::print(std::ostream& out, bool verbose, if (hadParentheses()) out << ')'; } -SearchColumnValueNode::SearchColumnValueNode( - const BucketIdFactory& bucketIdFactory, - const vespalib::stringref & name, int numColumns) - : _bucketIdFactory(bucketIdFactory), - _id(name), - _numColumns(numColumns), - _distribution(std::make_unique<BucketDistribution>(_numColumns, 16)) -{ -} - -int64_t -SearchColumnValueNode::getValue(const BucketId& id) const -{ - return _distribution->getColumn(id); -} - - -std::unique_ptr<Value> -SearchColumnValueNode::getValue(const Context& context) const -{ - if (context._doc != NULL) { - return getValue(context._doc->getId()); - } else if (context._docId != NULL) { - return getValue(*context._docId); - } else { - return getValue(context._docUpdate->getId()); - } -} - - -std::unique_ptr<Value> -SearchColumnValueNode::getValue(const DocumentId& id) const -{ - return std::unique_ptr<Value>(new IntegerValue( - getValue(_bucketIdFactory.getBucketId(id)), false)); -} - - -std::unique_ptr<Value> -SearchColumnValueNode::traceValue(const Context& context, - std::ostream &out) const -{ - if (context._doc != NULL) { - return traceValue(context._doc->getId(), out); - } else if (context._docId != NULL) { - return traceValue(*context._docId, out); - } else { - return traceValue(context._docUpdate->getId(), out); - } -} - - -std::unique_ptr<Value> -SearchColumnValueNode::traceValue(const DocumentId& id, - std::ostream& out) const -{ - std::unique_ptr<Value> result(new IntegerValue( - getValue(_bucketIdFactory.getBucketId(id)), false)); - out << "Resolved search column of doc \"" << id << "\" to " << *result - << "\n"; - return result; -} - - -void -SearchColumnValueNode::visit(Visitor &visitor) const -{ - visitor.visitSearchColumnValueNode(*this); -} - - -void -SearchColumnValueNode::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - (void) verbose; (void) indent; - if (hadParentheses()) out << '('; - out << _id; - out << '.' << _numColumns; - if (hadParentheses()) out << ')'; -} - namespace { union HashUnion { unsigned char _key[16]; @@ -1176,5 +1090,45 @@ ArithmeticValueNode::print(std::ostream& out, bool verbose, if (hadParentheses()) out << ')'; } +FieldExprNode::~FieldExprNode() = default; + +std::unique_ptr<FieldValueNode> FieldExprNode::convert_to_field_value() const { + const auto& doctype = resolve_doctype(); + // FIXME deprecate manual post-parsing of field expressions in favor of + // actually using the structural parser in the way nature intended. + vespalib::string mangled_expression; + build_mangled_expression(mangled_expression); + return std::make_unique<FieldValueNode>(doctype, mangled_expression); +} + +std::unique_ptr<FunctionValueNode> FieldExprNode::convert_to_function_call() const { + // Right hand expr string contains function call, lhs contains field spec on which + // the function is to be invoked. + if ((_left_expr == nullptr) || (_left_expr->_left_expr == nullptr)) { + throw vespalib::IllegalArgumentException( + vespalib::make_string("Cannot call function '%s' directly on document type", _right_expr.c_str())); + } + auto lhs = _left_expr->convert_to_field_value(); + const auto& function_name = _right_expr; + return std::make_unique<FunctionValueNode>(function_name, std::move(lhs)); +} + +void FieldExprNode::build_mangled_expression(vespalib::string& dest) const { + // Leftmost node is doctype, which should not be emitted as part of mangled expression. + if (_left_expr && _left_expr->_left_expr) { + _left_expr->build_mangled_expression(dest); + dest.push_back('.'); + } + dest.append(_right_expr); +} + +const vespalib::string& FieldExprNode::resolve_doctype() const { + const auto* leftmost = this; + while (leftmost->_left_expr) { + leftmost = leftmost->_left_expr.get(); + } + return leftmost->_right_expr; +} + } diff --git a/document/src/vespa/document/select/valuenodes.h b/document/src/vespa/document/select/valuenodes.h index 0464159b85f..bc1ec0e01e8 100644 --- a/document/src/vespa/document/select/valuenodes.h +++ b/document/src/vespa/document/select/valuenodes.h @@ -35,9 +35,8 @@ public: class NullValueNode : public ValueNode { - vespalib::string _name; public: - NullValueNode(const vespalib::stringref & name); + NullValueNode(); std::unique_ptr<Value> getValue(const Context&) const override { return std::unique_ptr<Value>(new NullValue()); @@ -48,7 +47,7 @@ public: void visit(Visitor& visitor) const override; ValueNode::UP clone() const override { - return wrapParens(new NullValueNode(_name)); + return wrapParens(new NullValueNode()); } }; @@ -56,7 +55,7 @@ class StringValueNode : public ValueNode { vespalib::string _value; public: - StringValueNode(const vespalib::stringref & val); + explicit StringValueNode(const vespalib::stringref & val); const vespalib::string& getValue() const { return _value; } @@ -115,6 +114,7 @@ class VariableValueNode : public ValueNode { vespalib::string _value; public: + // TODO stringref VariableValueNode(const vespalib::string & variableName) : _value(variableName) {} const vespalib::string& getVariableName() const { return _value; } @@ -183,6 +183,59 @@ private: void initFieldPath(const DocumentType&) const; }; +class FunctionValueNode; + +// Only used by the parser to build a partial field expression. Never part of +// an AST tree returned to the caller. +class FieldExprNode final : public ValueNode { + std::unique_ptr<FieldExprNode> _left_expr; + vespalib::string _right_expr; +public: + explicit FieldExprNode(const vespalib::string& doctype) : _left_expr(), _right_expr(doctype) {} + FieldExprNode(std::unique_ptr<FieldExprNode> left_expr, vespalib::stringref right_expr) + : _left_expr(std::move(left_expr)), _right_expr(right_expr) + {} + FieldExprNode(const FieldExprNode &) = delete; + FieldExprNode & operator = (const FieldExprNode &) = delete; + FieldExprNode(FieldExprNode &&) = default; + FieldExprNode & operator = (FieldExprNode &&) = default; + ~FieldExprNode(); + + std::unique_ptr<FieldValueNode> convert_to_field_value() const; + std::unique_ptr<FunctionValueNode> convert_to_function_call() const; +private: + void build_mangled_expression(vespalib::string& dest) const; + const vespalib::string& resolve_doctype() const; + + // These are not used, can just return dummy values. + std::unique_ptr<Value> getValue(const Context& context) const override { + (void) context; + return std::unique_ptr<Value>(); + } + std::unique_ptr<Value> traceValue(const Context &context, std::ostream& out) const override { + (void) context; + (void) out; + return std::unique_ptr<Value>(); + } + void print(std::ostream& out, bool verbose, const std::string& indent) const override { + (void) out; + (void) verbose; + (void) indent; + } + void visit(Visitor& visitor) const override { + (void) visitor; + } + + ValueNode::UP clone() const override { + if (_left_expr) { + return wrapParens(new FieldExprNode(std::unique_ptr<FieldExprNode>( + static_cast<FieldExprNode*>(_left_expr->clone().release())), _right_expr)); + } else { + return wrapParens(new FieldExprNode(_right_expr)); + } + } +}; + class IdValueNode : public ValueNode { public: @@ -222,35 +275,6 @@ private: int _divisionBits; }; -class SearchColumnValueNode : public ValueNode -{ -public: - SearchColumnValueNode(const BucketIdFactory& bucketIdFactory, - const vespalib::stringref & name, - int numColumns); - - int getColumns() { return _numColumns; } - - std::unique_ptr<Value> getValue(const Context& context) const override; - std::unique_ptr<Value> getValue(const DocumentId& id) const; - std::unique_ptr<Value> traceValue(const Context& context, std::ostream &out) const override; - std::unique_ptr<Value> traceValue(const DocumentId& val, std::ostream& out) const; - - int64_t getValue(const BucketId& bucketId) const; - void print(std::ostream& out, bool verbose, const std::string& indent) const override; - void visit(Visitor& visitor) const override; - - ValueNode::UP clone() const override { - return wrapParens(new SearchColumnValueNode(_bucketIdFactory, _id, _numColumns)); -} - -private: - const BucketIdFactory& _bucketIdFactory; - vespalib::string _id; - int _numColumns; - std::unique_ptr<BucketDistribution> _distribution; -}; - class FunctionValueNode : public ValueNode { public: diff --git a/document/src/vespa/document/select/visitor.h b/document/src/vespa/document/select/visitor.h index c89f0f24a6f..762d47c7c35 100644 --- a/document/src/vespa/document/select/visitor.h +++ b/document/src/vespa/document/select/visitor.h @@ -70,9 +70,6 @@ public: visitIdValueNode(const IdValueNode &) = 0; virtual void - visitSearchColumnValueNode(const SearchColumnValueNode &) = 0; - - virtual void visitFieldValueNode(const FieldValueNode &) = 0; virtual void diff --git a/documentapi/pom.xml b/documentapi/pom.xml index c94337e8873..3781dfd93ac 100644 --- a/documentapi/pom.xml +++ b/documentapi/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>documentapi</artifactId> <packaging>container-plugin</packaging> diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp index 96408e9a204..62747309c13 100644 --- a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp +++ b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.cpp @@ -13,6 +13,8 @@ GetBucketListMessage::GetBucketListMessage(const document::BucketId &bucketId) : { } +GetBucketListMessage::~GetBucketListMessage() = default; + DocumentReply::UP GetBucketListMessage::doCreateReply() const { diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h index 8b49afd6672..e89020bcf37 100644 --- a/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h +++ b/documentapi/src/vespa/documentapi/messagebus/messages/getbucketlistmessage.h @@ -24,6 +24,8 @@ public: */ GetBucketListMessage(const document::BucketId &bucketId); + ~GetBucketListMessage(); + /** * Returns the bucket whose list to retrieve. * diff --git a/documentgen-test/pom.xml b/documentgen-test/pom.xml index 53b49cf1473..d009ef3d592 100644 --- a/documentgen-test/pom.xml +++ b/documentgen-test/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>documentgen-test</artifactId> <packaging>jar</packaging> diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt index 89e8a72e330..d107ebfed40 100644 --- a/eval/CMakeLists.txt +++ b/eval/CMakeLists.txt @@ -24,6 +24,7 @@ vespa_define_module( src/tests/eval/value_cache src/tests/eval/value_type src/tests/tensor/dense_dot_product_function + src/tests/tensor/dense_xw_product_function src/tests/tensor/dense_tensor_address_combiner src/tests/tensor/dense_tensor_builder src/tests/tensor/dense_tensor_function_compiler diff --git a/eval/src/apps/tensor_conformance/generate.cpp b/eval/src/apps/tensor_conformance/generate.cpp index 0aba5276ace..f70c472cbcd 100644 --- a/eval/src/apps/tensor_conformance/generate.cpp +++ b/eval/src/apps/tensor_conformance/generate.cpp @@ -169,6 +169,16 @@ void generate_dot_product(TestBuilder &dst) { //----------------------------------------------------------------------------- +void generate_xw_product(TestBuilder &dst) { + auto matrix = spec({x(2),y(3)}, Seq({ 3, 5, 7, 11, 13, 17 })); + dst.add("reduce(a*b,sum,x)", {{"a", spec(x(2), Seq({ 1, 2 }))}, {"b", matrix}}, + spec(y(3), Seq({(1*3+2*11),(1*5+2*13),(1*7+2*17)}))); + dst.add("reduce(a*b,sum,y)", {{"a", spec(y(3), Seq({ 1, 2, 3 }))}, {"b", matrix}}, + spec(x(2), Seq({(1*3+2*5+3*7),(1*11+2*13+3*17)}))); +} + +//----------------------------------------------------------------------------- + void generate_tensor_concat(TestBuilder &dst) { dst.add("concat(a,b,x)", {{"a", spec(10.0)}, {"b", spec(20.0)}}, spec(x(2), Seq({10.0, 20.0}))); dst.add("concat(a,b,x)", {{"a", spec(x(1), Seq({10.0}))}, {"b", spec(20.0)}}, spec(x(2), Seq({10.0, 20.0}))); @@ -218,6 +228,7 @@ Generator::generate(TestBuilder &dst) generate_tensor_map(dst); generate_tensor_join(dst); generate_dot_product(dst); + generate_xw_product(dst); generate_tensor_concat(dst); generate_tensor_rename(dst); generate_tensor_lambda(dst); diff --git a/eval/src/apps/tensor_conformance/test_spec.json b/eval/src/apps/tensor_conformance/test_spec.json index 24edc9a7ac7..513d5e8e902 100644 --- a/eval/src/apps/tensor_conformance/test_spec.json +++ b/eval/src/apps/tensor_conformance/test_spec.json @@ -1225,6 +1225,8 @@ {"expression":"reduce(a*b,sum)","inputs":{"a":"0x0201017803400000000000000040080000000000004014000000000000","b":"0x0201017803401C0000000000004026000000000000402A000000000000"},"result":{"expect":"0x0200405C000000000000"}} {"expression":"reduce(a*b,sum)","inputs":{"a":"0x020101780240000000000000004008000000000000","b":"0x0201017803401C0000000000004026000000000000402A000000000000"},"result":{"expect":"0x02004047800000000000"}} {"expression":"reduce(a*b,sum)","inputs":{"a":"0x0201017803400000000000000040080000000000004014000000000000","b":"0x0201017802401C0000000000004026000000000000"},"result":{"expect":"0x02004047800000000000"}} +{"expression":"reduce(a*b,sum,x)","inputs":{"a":"0x02010178023FF00000000000004000000000000000","b":"0x020201780201790340080000000000004014000000000000401C0000000000004026000000000000402A0000000000004031000000000000"},"result":{"expect":"0x02010179034039000000000000403F0000000000004044800000000000"}} +{"expression":"reduce(a*b,sum,y)","inputs":{"a":"0x02010179033FF000000000000040000000000000004008000000000000","b":"0x020201780201790340080000000000004014000000000000401C0000000000004026000000000000402A0000000000004031000000000000"},"result":{"expect":"0x020101780240410000000000004056000000000000"}} {"expression":"concat(a,b,x)","inputs":{"a":"0x02004024000000000000","b":"0x02004034000000000000"},"result":{"expect":"0x020101780240240000000000004034000000000000"}} {"expression":"concat(a,b,x)","inputs":{"a":"0x02010178014024000000000000","b":"0x02004034000000000000"},"result":{"expect":"0x020101780240240000000000004034000000000000"}} {"expression":"concat(a,b,x)","inputs":{"a":"0x02004024000000000000","b":"0x02010178014034000000000000"},"result":{"expect":"0x020101780240240000000000004034000000000000"}} @@ -1242,4 +1244,4 @@ {"expression":"tensor(x[10])(x+1)","inputs":{},"result":{"expect":"0x020101780A3FF000000000000040000000000000004008000000000000401000000000000040140000000000004018000000000000401C000000000000402000000000000040220000000000004024000000000000"}} {"expression":"tensor(x[5],y[4])(x*4+(y+1))","inputs":{},"result":{"expect":"0x02020178050179043FF000000000000040000000000000004008000000000000401000000000000040140000000000004018000000000000401C00000000000040200000000000004022000000000000402400000000000040260000000000004028000000000000402A000000000000402C000000000000402E00000000000040300000000000004031000000000000403200000000000040330000000000004034000000000000"}} {"expression":"tensor(x[5],y[4])(x==y)","inputs":{},"result":{"expect":"0x02020178050179043FF000000000000000000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000000000000000000000000000000000000000000000000000003FF00000000000000000000000000000000000000000000000000000000000000000000000000000"}} -{"num_tests":1244} +{"num_tests":1246} diff --git a/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp b/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp index 76f776df552..29bff7fbd69 100644 --- a/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp +++ b/eval/src/tests/eval/interpreted_function/interpreted_function_test.cpp @@ -151,73 +151,152 @@ TEST("require that basic addition works") { //----------------------------------------------------------------------------- -TEST("require that dot product like expression is not optimized for unknown types") { - const TensorEngine &engine = SimpleTensorEngine::ref(); - Function function = Function::parse("reduce(a*b,sum)"); - DoubleValue a(2.0); - DoubleValue b(3.0); - double expect = (2.0 * 3.0); - InterpretedFunction interpreted(engine, function, NodeTypes()); - EXPECT_EQUAL(4u, interpreted.program_size()); - InterpretedFunction::Context ctx(interpreted); - InterpretedFunction::SimpleObjectParams params({a,b}); - const Value &result = interpreted.eval(ctx, params); - EXPECT_TRUE(result.is_double()); - EXPECT_EQUAL(expect, result.as_double()); +struct InnerProduct { + const TensorEngine &engine; + Function function; + TensorSpec a; + TensorSpec b; + TensorSpec expect; + NodeTypes types; + InterpretedFunction interpreted; + ~InnerProduct() {} + InnerProduct(const vespalib::string &expr) + : engine(SimpleTensorEngine::ref()), + function(Function::parse({"a", "b"}, expr)), + a("null"), b("null"), expect("null"), + types(), + interpreted(engine, function, types) {} + InnerProduct(const vespalib::string &expr, + TensorSpec a_in, + TensorSpec b_in, + TensorSpec expect_in) + : engine(SimpleTensorEngine::ref()), + function(Function::parse(expr)), + a(a_in), b(b_in), expect(expect_in), + types(function, {ValueType::from_spec(a.type()), ValueType::from_spec(a.type())}), + interpreted(engine, function, types) {} + void verify_optimized() const { + EXPECT_EQUAL(1u, interpreted.program_size()); + InterpretedFunction::Context ctx(interpreted); + Value::UP va = engine.from_spec(a); + Value::UP vb = engine.from_spec(b); + InterpretedFunction::SimpleObjectParams params({*va,*vb}); + const Value &result = interpreted.eval(ctx, params); + EXPECT_EQUAL(engine.to_spec(result), expect); + } + void verify_not_optimized() const { + EXPECT_EQUAL(4u, interpreted.program_size()); + } +}; + +struct UntypedIP : InnerProduct { + UntypedIP(const vespalib::string &expr) : InnerProduct(expr) { + a = TensorSpec("double").add({}, 2.0); + b = TensorSpec("double").add({}, 3.0); + expect = TensorSpec("double").add({}, 6.0); + } +}; + +struct DotProduct : InnerProduct { + DotProduct(const vespalib::string &expr) + : InnerProduct(expr, + TensorSpec("tensor(x[3])") + .add({{"x", 0}}, 5.0) + .add({{"x", 1}}, 3.0) + .add({{"x", 2}}, 2.0), + TensorSpec("tensor(x[3])") + .add({{"x", 0}}, 7.0) + .add({{"x", 1}}, 11.0) + .add({{"x", 2}}, 13.0), + TensorSpec("double") + .add({}, (5.0 * 7.0) + (3.0 * 11.0) + (2.0 * 13.0))) {} +}; + +struct XW : InnerProduct { + XW(const vespalib::string &expr) + : InnerProduct(expr, + TensorSpec("tensor(x[2])") + .add({{"x", 0}}, 1.0) + .add({{"x", 1}}, 2.0), + TensorSpec("tensor(x[2],y[3])") + .add({{"y", 0},{"x", 0}}, 3.0) + .add({{"y", 0},{"x", 1}}, 5.0) + .add({{"y", 1},{"x", 0}}, 7.0) + .add({{"y", 1},{"x", 1}}, 11.0) + .add({{"y", 2},{"x", 0}}, 13.0) + .add({{"y", 2},{"x", 1}}, 17.0), + TensorSpec("tensor(y[3])") + .add({{"y", 0}}, (1.0 * 3.0) + (2.0 * 5.0)) + .add({{"y", 1}}, (1.0 * 7.0) + (2.0 * 11.0)) + .add({{"y", 2}}, (1.0 * 13.0) + (2.0 * 17.0))) {} +}; + +struct MatMul : InnerProduct { + MatMul(const vespalib::string &expr) + : InnerProduct(expr, + TensorSpec("tensor(x[2],y[2])") + .add({{"x", 0},{"y", 0}}, 1.0) + .add({{"x", 0},{"y", 1}}, 2.0) + .add({{"x", 1},{"y", 0}}, 3.0) + .add({{"x", 1},{"y", 1}}, 5.0), + TensorSpec("tensor(y[2],z[2])") + .add({{"y", 0},{"z", 0}}, 7.0) + .add({{"y", 0},{"z", 1}}, 11.0) + .add({{"y", 1},{"z", 0}}, 13.0) + .add({{"y", 1},{"z", 1}}, 17.0), + TensorSpec("tensor(x[2],z[2])") + .add({{"x", 0},{"z", 0}}, (1.0 * 7.0) + (2.0 * 13.0)) + .add({{"x", 0},{"z", 1}}, (1.0 * 11.0) + (2.0 * 17.0)) + .add({{"x", 1},{"z", 0}}, (3.0 * 7.0) + (5.0 * 13.0)) + .add({{"x", 1},{"z", 1}}, (3.0 * 11.0) + (5.0 * 17.0))) {} +}; + +TEST("require that inner product is not optimized for unknown types") { + TEST_DO(UntypedIP("reduce(a*b,sum)").verify_not_optimized()); + TEST_DO(UntypedIP("reduce(join(a,b,f(x,y)(x*y)),sum)").verify_not_optimized()); } TEST("require that dot product works with tensor function") { - const TensorEngine &engine = SimpleTensorEngine::ref(); - Function function = Function::parse("reduce(a*b,sum)"); - auto a = TensorSpec("tensor(x[3])") - .add({{"x", 0}}, 5.0) - .add({{"x", 1}}, 3.0) - .add({{"x", 2}}, 2.0); - auto b = TensorSpec("tensor(x[3])") - .add({{"x", 0}}, 7.0) - .add({{"x", 1}}, 11.0) - .add({{"x", 2}}, 13.0); - double expect = ((5.0 * 7.0) + (3.0 * 11.0) + (2.0 * 13.0)); - NodeTypes types(function, {ValueType::from_spec(a.type()), ValueType::from_spec(a.type())}); - InterpretedFunction interpreted(engine, function, types); - EXPECT_EQUAL(1u, interpreted.program_size()); - InterpretedFunction::Context ctx(interpreted); - Value::UP va = engine.from_spec(a); - Value::UP vb = engine.from_spec(b); - InterpretedFunction::SimpleObjectParams params({*va,*vb}); - const Value &result = interpreted.eval(ctx, params); - EXPECT_TRUE(result.is_double()); - EXPECT_EQUAL(expect, result.as_double()); + TEST_DO(DotProduct("reduce(a*b,sum)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*y)),sum)").verify_optimized()); + TEST_DO(DotProduct("reduce(b*a,sum)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(x*y)),sum)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(y*x)),sum)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(y*x)),sum)").verify_optimized()); + TEST_DO(DotProduct("reduce(a*b,sum,x)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*y)),sum,x)").verify_optimized()); + TEST_DO(DotProduct("reduce(b*a,sum,x)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(x*y)),sum,x)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(y*x)),sum,x)").verify_optimized()); + TEST_DO(DotProduct("reduce(join(b,a,f(x,y)(y*x)),sum,x)").verify_optimized()); +} + +TEST("require that vector matrix multiplication works with tensor function") { + TEST_DO(XW("reduce(a*b,sum,x)").verify_optimized()); + TEST_DO(XW("reduce(join(a,b,f(x,y)(x*y)),sum,x)").verify_optimized()); + TEST_DO(XW("reduce(b*a,sum,x)").verify_optimized()); + TEST_DO(XW("reduce(join(b,a,f(x,y)(x*y)),sum,x)").verify_optimized()); + TEST_DO(XW("reduce(join(a,b,f(x,y)(y*x)),sum,x)").verify_optimized()); + TEST_DO(XW("reduce(join(b,a,f(x,y)(y*x)),sum,x)").verify_optimized()); } TEST("require that matrix multiplication works with tensor function") { - const TensorEngine &engine = SimpleTensorEngine::ref(); - Function function = Function::parse("reduce(a*b,sum,y)"); - auto a = TensorSpec("tensor(x[2],y[2])") - .add({{"x", 0},{"y", 0}}, 1.0) - .add({{"x", 0},{"y", 1}}, 2.0) - .add({{"x", 1},{"y", 0}}, 3.0) - .add({{"x", 1},{"y", 1}}, 5.0); - auto b = TensorSpec("tensor(y[2],z[2])") - .add({{"y", 0},{"z", 0}}, 7.0) - .add({{"y", 0},{"z", 1}}, 11.0) - .add({{"y", 1},{"z", 0}}, 13.0) - .add({{"y", 1},{"z", 1}}, 17.0); - auto expect = TensorSpec("tensor(x[2],z[2])") - .add({{"x", 0},{"z", 0}}, (1.0 * 7.0) + (2.0 * 13.0)) - .add({{"x", 0},{"z", 1}}, (1.0 * 11.0) + (2.0 * 17.0)) - .add({{"x", 1},{"z", 0}}, (3.0 * 7.0) + (5.0 * 13.0)) - .add({{"x", 1},{"z", 1}}, (3.0 * 11.0) + (5.0 * 17.0)); - NodeTypes types(function, {ValueType::from_spec(a.type()), ValueType::from_spec(a.type())}); - InterpretedFunction interpreted(engine, function, types); - EXPECT_EQUAL(1u, interpreted.program_size()); - InterpretedFunction::Context ctx(interpreted); - Value::UP va = engine.from_spec(a); - Value::UP vb = engine.from_spec(b); - InterpretedFunction::SimpleObjectParams params({*va,*vb}); - const Value &result = interpreted.eval(ctx, params); - ASSERT_TRUE(result.is_tensor()); - EXPECT_EQUAL(expect, engine.to_spec(result)); + TEST_DO(MatMul("reduce(a*b,sum,y)").verify_optimized()); + TEST_DO(MatMul("reduce(join(a,b,f(x,y)(x*y)),sum,y)").verify_optimized()); + TEST_DO(MatMul("reduce(b*a,sum,y)").verify_optimized()); + TEST_DO(MatMul("reduce(join(b,a,f(x,y)(x*y)),sum,y)").verify_optimized()); + TEST_DO(MatMul("reduce(join(a,b,f(x,y)(y*x)),sum,y)").verify_optimized()); + TEST_DO(MatMul("reduce(join(b,a,f(x,y)(y*x)),sum,y)").verify_optimized()); +} + +TEST("require that expressions similar to inner product are not optimized") { + TEST_DO(DotProduct("reduce(a*b,prod)").verify_not_optimized()); + TEST_DO(DotProduct("reduce(a*b,max)").verify_not_optimized()); + TEST_DO(DotProduct("reduce(a+b,sum)").verify_not_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x+y)),sum)").verify_not_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*x)),sum)").verify_not_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(y*y)),sum)").verify_not_optimized()); + TEST_DO(DotProduct("reduce(join(a,b,f(x,y)(x*y*1)),sum)").verify_not_optimized()); } //----------------------------------------------------------------------------- diff --git a/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp b/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp index de6fe4f175a..6f3cdd5f93f 100644 --- a/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp +++ b/eval/src/tests/tensor/dense_tensor_builder/dense_tensor_builder_test.cpp @@ -240,7 +240,7 @@ TEST_F("require that dimensions are sorted", Fixture) assertTensor({{"x", 5}, {"y", 3}}, {10, 11, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, denseTensor); - EXPECT_EQUAL("tensor(x[5],y[3])", denseTensor.getType().to_spec()); + EXPECT_EQUAL("tensor(x[5],y[3])", denseTensor.type().to_spec()); } diff --git a/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp b/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp index 63829650cc5..7df436d85a1 100644 --- a/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp +++ b/eval/src/tests/tensor/dense_tensor_function_compiler/dense_tensor_function_compiler_test.cpp @@ -2,6 +2,7 @@ #include <vespa/vespalib/testkit/test_kit.h> #include <vespa/eval/tensor/dense/dense_dot_product_function.h> +#include <vespa/eval/tensor/dense/dense_xw_product_function.h> #include <vespa/eval/tensor/dense/dense_tensor_function_compiler.h> #include <vespa/eval/eval/operation.h> @@ -11,8 +12,7 @@ using namespace vespalib::eval::tensor_function; using namespace vespalib::tensor; using vespalib::Stash; -template <typename T> -const T *as(const TensorFunction &function) { return dynamic_cast<const T *>(&function); } +//----------------------------------------------------------------------------- const TensorFunction & compileDotProduct(const vespalib::string &lhsType, @@ -48,6 +48,65 @@ assertNotCompiledDotProduct(const vespalib::string &lhsType, EXPECT_TRUE(reduce); } +//----------------------------------------------------------------------------- + +const TensorFunction & +compileXWProduct(const vespalib::string &lhsType, + const vespalib::string &rhsType, + const vespalib::string &dim, + Stash &stash) +{ + const Node &reduceNode = reduce(join(inject(ValueType::from_spec(lhsType), 1, stash), + inject(ValueType::from_spec(rhsType), 3, stash), + Mul::f, stash), + Aggr::SUM, {dim}, stash); + return DenseTensorFunctionCompiler::compile(reduceNode, stash); +} + +void +assertCompiledXWProduct(const vespalib::string &vecTypeStr, + const vespalib::string &matTypeStr, + const vespalib::string &dim) +{ + Stash stash; + const TensorFunction &func = compileXWProduct(vecTypeStr, matTypeStr, dim, stash); + const TensorFunction &inv_func = compileXWProduct(matTypeStr, vecTypeStr, dim, stash); + const DenseXWProductFunction *xwProduct = as<DenseXWProductFunction>(func); + const DenseXWProductFunction *inv_xwProduct = as<DenseXWProductFunction>(inv_func); + ValueType vecType = ValueType::from_spec(vecTypeStr); + ValueType matType = ValueType::from_spec(matTypeStr); + size_t common_idx = matType.dimension_index(vecType.dimensions()[0].name); + ASSERT_TRUE(xwProduct); + ASSERT_TRUE(inv_xwProduct); + ASSERT_TRUE(common_idx != ValueType::Dimension::npos); + EXPECT_EQUAL(xwProduct->vectorId(), 1u); + EXPECT_EQUAL(inv_xwProduct->vectorId(), 3u); + EXPECT_EQUAL(xwProduct->matrixId(), 3u); + EXPECT_EQUAL(inv_xwProduct->matrixId(), 1u); + EXPECT_EQUAL(xwProduct->vectorSize(), vecType.dimensions()[0].size); + EXPECT_EQUAL(inv_xwProduct->vectorSize(), vecType.dimensions()[0].size); + EXPECT_EQUAL(xwProduct->resultSize(), matType.dimensions()[1 - common_idx].size); + EXPECT_EQUAL(inv_xwProduct->resultSize(), matType.dimensions()[1 - common_idx].size); + EXPECT_EQUAL(xwProduct->matrixHasCommonDimensionInnermost(), (common_idx == 1)); + EXPECT_EQUAL(inv_xwProduct->matrixHasCommonDimensionInnermost(), (common_idx == 1)); +} + +void +assertNotCompiledXWProduct(const vespalib::string &vecType, + const vespalib::string &matType, + const vespalib::string &dim) +{ + Stash stash; + const TensorFunction &func = compileXWProduct(vecType, matType, dim, stash); + const TensorFunction &inv_func = compileXWProduct(matType, vecType, dim, stash); + const Reduce *reduce = as<Reduce>(func); + const Reduce *inv_reduce = as<Reduce>(inv_func); + EXPECT_TRUE(reduce); + EXPECT_TRUE(inv_reduce); +} + +//----------------------------------------------------------------------------- + TEST("require that dot product with compatible dimensions is compiled") { TEST_DO(assertCompiledDotProduct("tensor(x[5])", "tensor(x[5])")); @@ -67,4 +126,28 @@ TEST("require that dot product with incompatible dimensions is NOT compiled") TEST_DO(assertNotCompiledDotProduct("tensor(x[5],y[7])", "tensor(x[5],y[7])")); } +//----------------------------------------------------------------------------- + +TEST("require that xw products with compatible dimensions are compiled") { + TEST_DO(assertCompiledXWProduct("tensor(x[3])", "tensor(x[3],y[4])", "x")); + TEST_DO(assertCompiledXWProduct("tensor(y[4])", "tensor(x[3],y[4])", "y")); +} + +TEST("require that xw products with incompatible dimensions are not compiled") { + TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(x[3],y[4])", "y")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[])", "tensor(x[3],y[4])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(x[],y[4])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(x[3],y[])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[2])", "tensor(x[3],y[4])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[4])", "tensor(x[3],y[4])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(y[3],z[4])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(y[3],z[4])", "y")); + TEST_DO(assertNotCompiledXWProduct("tensor(x[3])", "tensor(y[3],z[4])", "z")); + TEST_DO(assertNotCompiledXWProduct("tensor(y[4])", "tensor(x[3],y[4])", "x")); + TEST_DO(assertNotCompiledXWProduct("tensor(y[3])", "tensor(x[3],y[4])", "y")); + TEST_DO(assertNotCompiledXWProduct("tensor(y[5])", "tensor(x[3],y[4])", "y")); +} + +//----------------------------------------------------------------------------- + TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_xw_product_function/CMakeLists.txt b/eval/src/tests/tensor/dense_xw_product_function/CMakeLists.txt new file mode 100644 index 00000000000..648c10e64bd --- /dev/null +++ b/eval/src/tests/tensor/dense_xw_product_function/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(eval_dense_xw_product_function_test_app TEST + SOURCES + dense_xw_product_function_test.cpp + DEPENDS + vespaeval +) +vespa_add_test(NAME eval_dense_xw_product_function_test_app COMMAND eval_dense_xw_product_function_test_app) diff --git a/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp b/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp new file mode 100644 index 00000000000..5c62d319dc3 --- /dev/null +++ b/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp @@ -0,0 +1,102 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/log/log.h> +LOG_SETUP("dense_dot_product_function_test"); + +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/eval/eval/tensor_function.h> +#include <vespa/eval/eval/operation.h> +#include <vespa/eval/eval/simple_tensor.h> +#include <vespa/eval/eval/simple_tensor_engine.h> +#include <vespa/eval/tensor/default_tensor_engine.h> +#include <vespa/eval/tensor/dense/dense_xw_product_function.h> +#include <vespa/eval/tensor/dense/dense_tensor.h> +#include <vespa/eval/tensor/dense/dense_tensor_builder.h> +#include <vespa/eval/tensor/dense/dense_tensor_view.h> + +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/vespalib/util/stash.h> + +using namespace vespalib; +using namespace vespalib::eval; +using namespace vespalib::tensor; + +const TensorEngine &ref_engine = SimpleTensorEngine::ref(); +const TensorEngine &prod_engine = DefaultTensorEngine::ref(); + +void verify_equal(const Value &expect, const Value &value) { + const eval::Tensor *tensor = value.as_tensor(); + ASSERT_TRUE(tensor != nullptr); + const eval::Tensor *expect_tensor = expect.as_tensor(); + ASSERT_TRUE(expect_tensor != nullptr); + auto expect_spec = expect_tensor->engine().to_spec(expect); + auto value_spec = tensor->engine().to_spec(value); + EXPECT_EQUAL(expect_spec, value_spec); +} + +std::vector<eval::Value::CREF> wrap(std::vector<eval::Value::CREF> params) { + return std::move(params); +} + +void verify_result(const TensorSpec &v, const TensorSpec &m, bool happy) { + Stash stash; + Value::UP ref_vec = ref_engine.from_spec(v); + Value::UP ref_mat = ref_engine.from_spec(m); + const Value &joined = ref_engine.join(*ref_vec, *ref_mat, operation::Mul::f, stash); + const Value &expect = ref_engine.reduce(joined, Aggr::SUM, {"x"}, stash); + + Value::UP prod_vec = prod_engine.from_spec(v); + Value::UP prod_mat = prod_engine.from_spec(m); + + DenseXWProductFunction fun1(expect.type(), 0, 1, + prod_vec->type().dimensions()[0].size, + expect.type().dimensions()[0].size, + happy); + const Value &actual1 = fun1.eval(wrap({*prod_vec, *prod_mat}), stash); + TEST_DO(verify_equal(expect, actual1)); + + DenseXWProductFunction fun2(expect.type(), 1, 0, + prod_vec->type().dimensions()[0].size, + expect.type().dimensions()[0].size, + happy); + const Value &actual2 = fun2.eval(wrap({*prod_mat, *prod_vec}), stash); + TEST_DO(verify_equal(expect, actual2)); +} + +TensorSpec make_vector(const vespalib::string &name, size_t sz) { + TensorSpec ret(make_string("tensor(%s[%zu])", name.c_str(), sz)); + for (size_t i = 0; i < sz; ++i) { + ret.add({{name, i}}, (1.0 + i) * 16.0); + } + return ret; +} + +TensorSpec make_matrix(const vespalib::string &d1name, size_t d1sz, + const vespalib::string &d2name, size_t d2sz) +{ + TensorSpec ret(make_string("tensor(%s[%zu],%s[%zu])", + d1name.c_str(), d1sz, + d2name.c_str(), d2sz)); + for (size_t i = 0; i < d1sz; ++i) { + for (size_t j = 0; j < d2sz; ++j) { + ret.add({{d1name,i},{d2name,j}}, 1.0 + i*7.0 + j*43.0); + } + } + return ret; +} + +TEST("require that xw product gives same results as reference join/reduce") { + verify_result(make_vector("x", 1), make_matrix("o", 1, "x", 1), true); + verify_result(make_vector("x", 1), make_matrix("x", 1, "y", 1), false); + + verify_result(make_vector("x", 3), make_matrix("o", 2, "x", 3), true); + verify_result(make_vector("x", 3), make_matrix("x", 3, "y", 2), false); + + verify_result(make_vector("x", 5), make_matrix("o", 8, "x", 5), true); + verify_result(make_vector("x", 5), make_matrix("x", 5, "y", 8), false); + + verify_result(make_vector("x", 16), make_matrix("o", 5, "x", 16), true); + verify_result(make_vector("x", 16), make_matrix("x", 16, "y", 5), false); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp b/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp index b6d58236c9c..bd3d1ada017 100644 --- a/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp +++ b/eval/src/tests/tensor/sparse_tensor_builder/sparse_tensor_builder_test.cpp @@ -91,7 +91,7 @@ TEST("require that dimensions are extracted") EXPECT_EQUAL("a", dims[0].name); EXPECT_EQUAL("b", dims[1].name); EXPECT_EQUAL("c", dims[2].name); - EXPECT_EQUAL("tensor(a{},b{},c{})", sparseTensor.getType().to_spec()); + EXPECT_EQUAL("tensor(a{},b{},c{})", sparseTensor.type().to_spec()); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/vespa/eval/eval/interpreted_function.cpp b/eval/src/vespa/eval/eval/interpreted_function.cpp index cfe989e95f8..ada942767fa 100644 --- a/eval/src/vespa/eval/eval/interpreted_function.cpp +++ b/eval/src/vespa/eval/eval/interpreted_function.cpp @@ -161,25 +161,36 @@ struct ProgramBuilder : public NodeVisitor, public NodeTraverser { //------------------------------------------------------------------------- + bool is_mul_join(const Node &node) const { + if (auto join = as<TensorJoin>(node)) { + if (auto mul = as<Mul>(join->lambda().root())) { + auto sym1 = as<Symbol>(mul->lhs()); + auto sym2 = as<Symbol>(mul->rhs()); + return (sym1 && sym2 && (sym1->id() != sym2->id())); + } + } + return false; + } + + bool is_mul(const Node &node) const { + auto mul = as<Mul>(node); + return (mul || is_mul_join(node)); + } + bool is_typed_tensor(const Node &node) const { const ValueType &type = types.get_type(node); return (type.is_tensor() && !type.dimensions().empty()); } - bool is_typed(const Node &node) const { - return (types.get_type(node).is_double() || is_typed_tensor(node)); - } - bool is_typed_tensor_param(const Node &node) const { auto sym = as<Symbol>(node); return (sym && is_typed_tensor(node)); } bool is_typed_tensor_product_of_params(const Node &node) const { - auto mul = as<Mul>(node); - return (mul && is_typed_tensor(*mul) && - is_typed_tensor_param(mul->lhs()) && - is_typed_tensor_param(mul->rhs())); + return (is_typed_tensor(node) && is_mul(node) && + is_typed_tensor_param(node.get_child(0)) && + is_typed_tensor_param(node.get_child(1))); } //------------------------------------------------------------------------- @@ -260,7 +271,7 @@ struct ProgramBuilder : public NodeVisitor, public NodeTraverser { make_join_op(node, token.get()->get().get_function<2>()); } void visit(const TensorReduce &node) override { - if ((node.aggr() == Aggr::SUM) && is_typed(node) && is_typed_tensor_product_of_params(node.get_child(0))) { + if ((node.aggr() == Aggr::SUM) && is_typed_tensor_product_of_params(node.get_child(0))) { assert(program.size() >= 3); // load,load,mul program.pop_back(); // mul program.pop_back(); // load diff --git a/eval/src/vespa/eval/eval/tensor_function.h b/eval/src/vespa/eval/eval/tensor_function.h index 359cabc18a0..e23dc8c6fc0 100644 --- a/eval/src/vespa/eval/eval/tensor_function.h +++ b/eval/src/vespa/eval/eval/tensor_function.h @@ -45,9 +45,13 @@ struct TensorFunction virtual ~TensorFunction() {} }; -//----------------------------------------------------------------------------- +/** + * Simple typecasting utility. + */ +template <typename T> +const T *as(const TensorFunction &node) { return dynamic_cast<const T *>(&node); } -struct TensorFunctionVisitor; +//----------------------------------------------------------------------------- namespace tensor_function { @@ -77,12 +81,6 @@ struct Node : public TensorFunction Node &operator=(Node &&) = delete; }; -/** - * Simple typecasting utility. - */ -template <typename T> -const T *as(const Node &node) { return dynamic_cast<const T *>(&node); } - struct Inject : Node { const size_t tensor_id; Inject(const ValueType &result_type_in, diff --git a/eval/src/vespa/eval/eval/value_type.h b/eval/src/vespa/eval/eval/value_type.h index 370804b9c5e..2988cc5204e 100644 --- a/eval/src/vespa/eval/eval/value_type.h +++ b/eval/src/vespa/eval/eval/value_type.h @@ -45,6 +45,10 @@ private: : _type(type_in), _dimensions(std::move(dimensions_in)) {} public: + ValueType(ValueType &&) = default; + ValueType(const ValueType &) = default; + ValueType &operator=(ValueType &&) = default; + ValueType &operator=(const ValueType &) = default; ~ValueType(); Type type() const { return _type; } bool is_any() const { return (_type == Type::ANY); } diff --git a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp index 2506e6fcf0e..773d2364b7d 100644 --- a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp +++ b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp @@ -12,6 +12,7 @@ #include <vespa/eval/eval/tensor_spec.h> #include <vespa/eval/eval/simple_tensor_engine.h> #include <vespa/eval/eval/operation.h> +#include <vespa/vespalib/objects/nbostream.h> #include <cassert> @@ -42,8 +43,9 @@ const Value &to_simple(const Value &value, Stash &stash) { if (auto wrapped = dynamic_cast<const WrappedSimpleTensor *>(tensor)) { return wrapped->get(); } - TensorSpec spec = tensor->engine().to_spec(*tensor); - return *stash.create<Value::UP>(eval::SimpleTensor::create(spec)); + nbostream data; + tensor->engine().encode(*tensor, data); + return *stash.create<Value::UP>(eval::SimpleTensor::decode(data)); } return value; } @@ -57,8 +59,9 @@ const Value &to_default(const Value &value, Stash &stash) { return stash.create<WrappedSimpleTensor>(*simple); } } - TensorSpec spec = tensor->engine().to_spec(*tensor); - return *stash.create<Value::UP>(default_engine().from_spec(spec)); + nbostream data; + tensor->engine().encode(*tensor, data); + return *stash.create<Value::UP>(default_engine().decode(data)); } return value; } @@ -67,7 +70,7 @@ const Value &to_value(std::unique_ptr<Tensor> tensor, Stash &stash) { if (!tensor) { return ErrorValue::instance; } - if (tensor->getType().is_tensor()) { + if (tensor->type().is_tensor()) { return *stash.create<Value::UP>(std::move(tensor)); } return stash.create<DoubleValue>(tensor->as_double()); @@ -218,7 +221,7 @@ DefaultTensorEngine::map(const Value &a, map_fun_t function, Stash &stash) const } else if (auto tensor = a.as_tensor()) { assert(&tensor->engine() == this); const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(*tensor); - if (!tensor::Tensor::supported({my_a.getType()})) { + if (!tensor::Tensor::supported({my_a.type()})) { return to_default(simple_engine().map(to_simple(a, stash), function, stash), stash); } CellFunctionFunAdapter cell_function(function); @@ -237,7 +240,7 @@ DefaultTensorEngine::join(const Value &a, const Value &b, join_fun_t function, S } else if (auto tensor_b = b.as_tensor()) { assert(&tensor_b->engine() == this); const tensor::Tensor &my_b = static_cast<const tensor::Tensor &>(*tensor_b); - if (!tensor::Tensor::supported({my_b.getType()})) { + if (!tensor::Tensor::supported({my_b.type()})) { return fallback_join(a, b, function, stash); } CellFunctionBindLeftAdapter cell_function(function, a.as_double()); @@ -249,7 +252,7 @@ DefaultTensorEngine::join(const Value &a, const Value &b, join_fun_t function, S assert(&tensor_a->engine() == this); const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(*tensor_a); if (b.is_double()) { - if (!tensor::Tensor::supported({my_a.getType()})) { + if (!tensor::Tensor::supported({my_a.type()})) { return fallback_join(a, b, function, stash); } CellFunctionBindRightAdapter cell_function(function, b.as_double()); @@ -257,18 +260,10 @@ DefaultTensorEngine::join(const Value &a, const Value &b, join_fun_t function, S } else if (auto tensor_b = b.as_tensor()) { assert(&tensor_b->engine() == this); const tensor::Tensor &my_b = static_cast<const tensor::Tensor &>(*tensor_b); - if (!tensor::Tensor::supported({my_a.getType(), my_b.getType()})) { + if (!tensor::Tensor::supported({my_a.type(), my_b.type()})) { return fallback_join(a, b, function, stash); } - if (function == eval::operation::Mul::f) { - if (my_a.getType() == my_b.getType()) { - return to_value(my_a.match(my_b), stash); - } else { - return to_value(my_a.multiply(my_b), stash); - } - } else { - return to_value(my_a.join(function, my_b), stash); - } + return to_value(my_a.join(function, my_b), stash); } else { return ErrorValue::instance; } @@ -291,16 +286,14 @@ DefaultTensorEngine::reduce(const Value &a, Aggr aggr, const std::vector<vespali } else if (auto tensor = a.as_tensor()) { assert(&tensor->engine() == this); const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(*tensor); - if (!tensor::Tensor::supported({my_a.getType()})) { + if (!tensor::Tensor::supported({my_a.type()})) { return fallback_reduce(a, aggr, dimensions, stash); } switch (aggr) { case Aggr::PROD: return to_value(my_a.reduce(eval::operation::Mul::f, dimensions), stash); case Aggr::SUM: if (dimensions.empty()) { - return stash.create<eval::DoubleValue>(my_a.sum()); - } else if (dimensions.size() == 1) { - return to_value(my_a.sum(dimensions[0]), stash); + return stash.create<eval::DoubleValue>(my_a.as_double()); } else { return to_value(my_a.reduce(eval::operation::Add::f, dimensions), stash); } @@ -314,9 +307,48 @@ DefaultTensorEngine::reduce(const Value &a, Aggr aggr, const std::vector<vespali } } +size_t vector_size(const ValueType &type, const vespalib::string &dimension) { + if (type.is_double()) { + return 1; + } else if ((type.dimensions().size() == 1) && + (type.dimensions()[0].is_indexed()) && + (type.dimensions()[0].name == dimension)) + { + return type.dimensions()[0].size; + } else { + return 0; + } +} + +void append_vector(double *&pos, const Value &value) { + if (auto tensor = value.as_tensor()) { + const DenseTensorView *view = static_cast<const DenseTensorView *>(tensor); + for (double cell: view->cellsRef()) { + *pos++ = cell; + } + } else { + *pos++ = value.as_double(); + } +} + +const Value &concat_vectors(const Value &a, const Value &b, const vespalib::string &dimension, size_t vector_size, Stash &stash) { + ArrayRef<double> cells = stash.create_array<double>(vector_size); + double *pos = cells.begin(); + append_vector(pos, a); + append_vector(pos, b); + assert(pos == cells.end()); + const ValueType &type = stash.create<ValueType>(ValueType::tensor_type({ValueType::Dimension(dimension, vector_size)})); + return stash.create<DenseTensorView>(type, cells); +} + const Value & DefaultTensorEngine::concat(const Value &a, const Value &b, const vespalib::string &dimension, Stash &stash) const { + size_t a_size = vector_size(a.type(), dimension); + size_t b_size = vector_size(b.type(), dimension); + if ((a_size > 0) && (b_size > 0)) { + return concat_vectors(a, b, dimension, a_size + b_size, stash); + } return to_default(simple_engine().concat(to_simple(a, stash), to_simple(b, stash), dimension, stash), stash); } diff --git a/eval/src/vespa/eval/tensor/dense/CMakeLists.txt b/eval/src/vespa/eval/tensor/dense/CMakeLists.txt index ec715bce3d9..54698371f4e 100644 --- a/eval/src/vespa/eval/tensor/dense/CMakeLists.txt +++ b/eval/src/vespa/eval/tensor/dense/CMakeLists.txt @@ -3,6 +3,7 @@ vespa_add_library(eval_tensor_dense OBJECT SOURCES direct_dense_tensor_builder.cpp dense_dot_product_function.cpp + dense_xw_product_function.cpp dense_tensor.cpp dense_tensor_address_combiner.cpp dense_tensor_builder.cpp diff --git a/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp index 705496714fa..992f2eae750 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp @@ -23,9 +23,8 @@ namespace { CellsRef getCellsRef(const eval::Value &value) { - const Tensor *tensor = static_cast<const Tensor *>(value.as_tensor()); - const DenseTensorView *denseTensor = static_cast<const DenseTensorView *>(tensor); - return denseTensor->cellsRef(); + const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value); + return denseTensor.cellsRef(); } } diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp index e9ee7d30692..1268a46b8e5 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_function_compiler.cpp @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "dense_dot_product_function.h" +#include "dense_xw_product_function.h" #include "dense_tensor_function_compiler.h" #include <vespa/eval/eval/operation.h> #include <vespa/vespalib/test/insertion_operators.h> @@ -15,39 +16,64 @@ namespace tensor { namespace { -bool -willReduceAllDimensions(const std::vector<vespalib::string> &dimensions) -{ - return (dimensions.empty() || (dimensions.size() == 1)); +bool is1dDenseTensor(const ValueType &type) { + return (type.is_dense() && (type.dimensions().size() == 1)); } -bool -is1dDenseTensor(const ValueType &type) -{ - return (type.is_dense() && (type.dimensions().size() == 1)); +bool isConcreteDenseTensor(const ValueType &type, size_t d) { + return (type.is_dense() && (type.dimensions().size() == d) && !type.is_abstract()); } -bool -isCompatibleTensorsForDotProduct(const ValueType &lhsType, const ValueType &rhsType) -{ - return (is1dDenseTensor(lhsType) && +bool isDenseDotProduct(const ValueType &res, const ValueType &lhsType, const ValueType &rhsType) { + return (res.is_double() && + is1dDenseTensor(lhsType) && is1dDenseTensor(rhsType) && (lhsType.dimensions()[0].name == rhsType.dimensions()[0].name)); } -struct DotProductFunctionCompiler +bool isDenseXWProduct(const ValueType &res, const ValueType &vec, const ValueType &mat) { + if (isConcreteDenseTensor(res, 1) && + isConcreteDenseTensor(vec, 1) && + isConcreteDenseTensor(mat, 2)) + { + size_t res_idx = mat.dimension_index(res.dimensions()[0].name); + size_t vec_idx = mat.dimension_index(vec.dimensions()[0].name); + size_t npos = ValueType::Dimension::npos; + if ((res_idx != npos) && (vec_idx != npos) && (res_idx != vec_idx)) { + return ((mat.dimensions()[res_idx].size == res.dimensions()[0].size) && + (mat.dimensions()[vec_idx].size == vec.dimensions()[0].size)); + } + } + return false; +} + +const TensorFunction &createDenseXWProduct(const ValueType &res, const Inject &vec, const Inject &mat, Stash &stash) { + bool common_is_inner = (mat.result_type.dimension_index(vec.result_type.dimensions()[0].name) == 1); + return stash.create<DenseXWProductFunction>(res, vec.tensor_id, mat.tensor_id, + vec.result_type.dimensions()[0].size, + res.dimensions()[0].size, + common_is_inner); +} + +struct InnerProductFunctionCompiler { static const TensorFunction &compile(const Node &expr, Stash &stash) { const Reduce *reduce = as<Reduce>(expr); - if (reduce && (reduce->aggr == Aggr::SUM) && willReduceAllDimensions(reduce->dimensions)) { + if (reduce && (reduce->aggr == Aggr::SUM)) { const Join *join = as<Join>(reduce->tensor); if (join && (join->function == Mul::f)) { - const Inject *lhsTensor = as<Inject>(join->lhs_tensor); - const Inject *rhsTensor = as<Inject>(join->rhs_tensor); - if (lhsTensor && rhsTensor && - isCompatibleTensorsForDotProduct(lhsTensor->result_type, rhsTensor->result_type)) - { - return stash.create<DenseDotProductFunction>(lhsTensor->tensor_id, rhsTensor->tensor_id); + const Inject *lhs = as<Inject>(join->lhs_tensor); + const Inject *rhs = as<Inject>(join->rhs_tensor); + if (lhs && rhs) { + if (isDenseDotProduct(expr.result_type, lhs->result_type, rhs->result_type)) { + return stash.create<DenseDotProductFunction>(lhs->tensor_id, rhs->tensor_id); + } + if (isDenseXWProduct(expr.result_type, lhs->result_type, rhs->result_type)) { + return createDenseXWProduct(expr.result_type, *lhs, *rhs, stash); + } + if (isDenseXWProduct(expr.result_type, rhs->result_type, lhs->result_type)) { + return createDenseXWProduct(expr.result_type, *rhs, *lhs, stash); + } } } } @@ -60,7 +86,7 @@ struct DotProductFunctionCompiler const TensorFunction & DenseTensorFunctionCompiler::compile(const eval::tensor_function::Node &expr, Stash &stash) { - return DotProductFunctionCompiler::compile(expr, stash); + return InnerProductFunctionCompiler::compile(expr, stash); } } // namespace tensor diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp index 4402b5b0ae0..30c9f17348e 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp @@ -145,13 +145,13 @@ DenseTensorView::operator==(const DenseTensorView &rhs) const } const eval::ValueType & -DenseTensorView::getType() const +DenseTensorView::type() const { return _typeRef; } double -DenseTensorView::sum() const +DenseTensorView::as_double() const { double result = 0.0; for (const auto &cell : _cellsRef) { @@ -161,54 +161,6 @@ DenseTensorView::sum() const } Tensor::UP -DenseTensorView::add(const Tensor &arg) const -{ - return dense::apply(*this, arg, - [](double lhsValue, double rhsValue) - { return lhsValue + rhsValue; }); -} - -Tensor::UP -DenseTensorView::subtract(const Tensor &arg) const -{ - return dense::apply(*this, arg, - [](double lhsValue, double rhsValue) - { return lhsValue - rhsValue; }); -} - -Tensor::UP -DenseTensorView::multiply(const Tensor &arg) const -{ - return dense::apply(*this, arg, - [](double lhsValue, double rhsValue) - { return lhsValue * rhsValue; }); -} - -Tensor::UP -DenseTensorView::min(const Tensor &arg) const -{ - return dense::apply(*this, arg, - [](double lhsValue, double rhsValue) - { return std::min(lhsValue, rhsValue); }); -} - -Tensor::UP -DenseTensorView::max(const Tensor &arg) const -{ - return dense::apply(*this, arg, - [](double lhsValue, double rhsValue) - { return std::max(lhsValue, rhsValue); }); -} - -Tensor::UP -DenseTensorView::match(const Tensor &arg) const -{ - return joinDenseTensors(*this, arg, "match", - [](double lhsValue, double rhsValue) - { return (lhsValue * rhsValue); }); -} - -Tensor::UP DenseTensorView::apply(const CellFunction &func) const { Cells newCells(_cellsRef.size()); @@ -221,14 +173,6 @@ DenseTensorView::apply(const CellFunction &func) const return std::make_unique<DenseTensor>(_typeRef, std::move(newCells)); } -Tensor::UP -DenseTensorView::sum(const vespalib::string &dimension) const -{ - return dense::reduce(*this, { dimension }, - [](double lhsValue, double rhsValue) - { return lhsValue + rhsValue; }); -} - bool DenseTensorView::equals(const Tensor &arg) const { @@ -239,14 +183,6 @@ DenseTensorView::equals(const Tensor &arg) const return false; } -vespalib::string -DenseTensorView::toString() const -{ - std::ostringstream stream; - stream << *this; - return stream.str(); -} - Tensor::UP DenseTensorView::clone() const { @@ -271,7 +207,7 @@ buildAddress(const DenseTensorCellsIterator &itr, TensorSpec::Address &address) TensorSpec DenseTensorView::toSpec() const { - TensorSpec result(getType().to_spec()); + TensorSpec result(type().to_spec()); TensorSpec::Address address; for (CellsIterator itr(_typeRef, _cellsRef); itr.valid(); itr.next()) { buildAddress(itr, address); @@ -282,31 +218,6 @@ DenseTensorView::toSpec() const } void -DenseTensorView::print(std::ostream &out) const -{ - // TODO (geirst): print on common format. - out << "[ "; - bool first = true; - for (const auto &dim : _typeRef.dimensions()) { - if (!first) { - out << ", "; - } - out << dim.name << ":" << dim.size; - first = false; - } - out << " ] { "; - first = true; - for (const auto &cell : cellsRef()) { - if (!first) { - out << ", "; - } - out << cell; - first = false; - } - out << " }"; -} - -void DenseTensorView::accept(TensorVisitor &visitor) const { CellsIterator iterator(_typeRef, _cellsRef); @@ -330,6 +241,17 @@ DenseTensorView::accept(TensorVisitor &visitor) const Tensor::UP DenseTensorView::join(join_fun_t function, const Tensor &arg) const { + if (fast_type() == arg.type()) { + if (function == eval::operation::Mul::f) { + return joinDenseTensors(*this, arg, "mul", + [](double a, double b) { return (a * b); }); + } + if (function == eval::operation::Add::f) { + return joinDenseTensors(*this, arg, "add", + [](double a, double b) { return (a + b); }); + } + return joinDenseTensors(*this, arg, "join", function); + } return dense::apply(*this, arg, function); } diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h index 472cc58ad6b..5a59594667d 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h +++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h @@ -47,24 +47,15 @@ public: bool operator==(const DenseTensorView &rhs) const; CellsIterator cellsIterator() const { return CellsIterator(_typeRef, _cellsRef); } - virtual const eval::ValueType &getType() const override; - virtual double sum() const override; - virtual Tensor::UP add(const Tensor &arg) const override; - virtual Tensor::UP subtract(const Tensor &arg) const override; - virtual Tensor::UP multiply(const Tensor &arg) const override; - virtual Tensor::UP min(const Tensor &arg) const override; - virtual Tensor::UP max(const Tensor &arg) const override; - virtual Tensor::UP match(const Tensor &arg) const override; + virtual const eval::ValueType &type() const override; + virtual double as_double() const override; virtual Tensor::UP apply(const CellFunction &func) const override; - virtual Tensor::UP sum(const vespalib::string &dimension) const override; virtual Tensor::UP join(join_fun_t function, const Tensor &arg) const override; virtual Tensor::UP reduce(join_fun_t op, const std::vector<vespalib::string> &dimensions) const override; virtual bool equals(const Tensor &arg) const override; - virtual void print(std::ostream &out) const override; - virtual vespalib::string toString() const override; virtual Tensor::UP clone() const override; virtual eval::TensorSpec toSpec() const override; virtual void accept(TensorVisitor &visitor) const override; diff --git a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp new file mode 100644 index 00000000000..45de00dc7fe --- /dev/null +++ b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp @@ -0,0 +1,91 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "dense_xw_product_function.h" +#include "dense_tensor.h" +#include "dense_tensor_view.h" +#include <vespa/eval/eval/value.h> +#include <vespa/eval/tensor/tensor.h> +#include <vespa/vespalib/util/exceptions.h> +#include <assert.h> + +namespace vespalib { +namespace tensor { + +DenseXWProductFunction::DenseXWProductFunction(const eval::ValueType &resultType, + size_t vectorId, + size_t matrixId, + size_t vectorSize, + size_t resultSize, + bool matrixHasCommonDimensionInnermost) + : _resultType(resultType), + _vectorId(vectorId), + _matrixId(matrixId), + _vectorSize(vectorSize), + _resultSize(resultSize), + _commonDimensionInnermost(matrixHasCommonDimensionInnermost), + _hwAccelerator(hwaccelrated::IAccelrated::getAccelrator()) +{} + +void +DenseXWProductFunction::multiDotProduct(const XWInput &vectorCells, + const XWInput &matrixCells, + XWOutput &result) const +{ + double *out = result.begin(); + const double *matrixP = matrixCells.cbegin(); + const double * const vectorP = vectorCells.cbegin(); + for (size_t row = 0; row < _resultSize; ++row) { + double cell = _hwAccelerator->dotProduct(vectorP, matrixP, _vectorSize); + *out++ = cell; + matrixP += _vectorSize; + } + assert(out == result.end()); + assert(matrixP == matrixCells.cend()); +} + +void +DenseXWProductFunction::transposedProduct(const XWInput &vectorCells, + const XWInput &matrixCells, + XWOutput &result) const +{ + double *out = result.begin(); + const double * const matrixP = matrixCells.cbegin(); + const double * const vectorP = vectorCells.cbegin(); + for (size_t row = 0; row < _resultSize; ++row) { + double cell = 0; + for (size_t col = 0; col < _vectorSize; ++col) { + cell += matrixP[col*_resultSize + row] * vectorP[col]; + } + *out++ = cell; + } + assert(out == result.end()); +} + +namespace { + +DenseTensorView::CellsRef +getCellsRef(const eval::Value &value) +{ + const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value); + return denseTensor.cellsRef(); +} + +} // namespace <unnamed> + +const eval::Value & +DenseXWProductFunction::eval(ConstArrayRef<eval::Value::CREF> params, Stash &stash) const +{ + DenseTensorView::CellsRef vectorCells = getCellsRef(params[_vectorId]); + DenseTensorView::CellsRef matrixCells = getCellsRef(params[_matrixId]); + + ArrayRef<double> outputCells = stash.create_array<double>(_resultSize); + if (_commonDimensionInnermost) { + multiDotProduct(vectorCells, matrixCells, outputCells); + } else { + transposedProduct(vectorCells, matrixCells, outputCells); + } + return stash.create<DenseTensorView>(_resultType, outputCells); +} + +} // namespace tensor +} // namespace vespalib diff --git a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h new file mode 100644 index 00000000000..db006100e5a --- /dev/null +++ b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h @@ -0,0 +1,53 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/eval/eval/tensor_function.h> +#include "dense_tensor_view.h" +#include <vespa/vespalib/hwaccelrated/iaccelrated.h> + +namespace vespalib { +namespace tensor { + +using XWInput = DenseTensorView::CellsRef; +using XWOutput = ArrayRef<double>; + +/** + * Tensor function for product of one 1-dimensional and one 2-dimensional dense tensor. + */ +class DenseXWProductFunction : public eval::TensorFunction +{ +private: + const eval::ValueType _resultType; + const size_t _vectorId; + const size_t _matrixId; + const size_t _vectorSize; + const size_t _resultSize; + bool _commonDimensionInnermost; + hwaccelrated::IAccelrated::UP _hwAccelerator; + + void multiDotProduct(const XWInput &v, const XWInput &m, XWOutput &r) const; + void transposedProduct(const XWInput &v, const XWInput &m, XWOutput &r) const; +public: + DenseXWProductFunction(const eval::ValueType &resultType, + size_t vectorId, + size_t matrixId, + size_t vectorSize, + size_t resultSize, + bool matrixHasCommonDimensionInnermost); + + ~DenseXWProductFunction() {} + + size_t vectorId() const { return _vectorId; } + size_t matrixId() const { return _matrixId; } + + size_t vectorSize() const { return _vectorSize; } + size_t resultSize() const { return _resultSize; } + + bool matrixHasCommonDimensionInnermost() const { return _commonDimensionInnermost; } + + const eval::Value &eval(ConstArrayRef<eval::Value::CREF> params, Stash &stash) const override; +}; + +} // namespace tensor +} // namespace vespalib diff --git a/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp b/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp index c7be1580f19..94ca309ecbc 100644 --- a/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp +++ b/eval/src/vespa/eval/tensor/serialization/slime_binary_format.cpp @@ -75,7 +75,7 @@ SlimeBinaryFormatSerializer::visit(const TensorAddress &address, void SlimeBinaryFormatSerializer::serialize(const Tensor &tensor) { - eval::ValueType type(tensor.getType()); + eval::ValueType type(tensor.type()); for (const auto & dimension : type.dimensions()) { _dimensions.addString(Memory(dimension.name)); } diff --git a/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp b/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp index bcb95eeed29..bd0c5b25f93 100644 --- a/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp +++ b/eval/src/vespa/eval/tensor/serialization/sparse_binary_format.cpp @@ -77,7 +77,7 @@ void SparseBinaryFormatSerializer::serialize(nbostream &stream, const Tensor &tensor) { - _type = tensor.getType(); + _type = tensor.type(); tensor.accept(*this); stream.putInt1_4Bytes(_type.dimensions().size()); for (const auto &dimension : _type.dimensions()) { diff --git a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp index 84aab8826c3..4762f1eceb4 100644 --- a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp +++ b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.cpp @@ -33,27 +33,6 @@ copyCells(Cells &cells, const Cells &cells_in, Stash &stash) } } -void -printAddress(std::ostream &out, const SparseTensorAddressRef &ref, - const eval::ValueType &type) -{ - out << "{"; - bool first = true; - SparseTensorAddressDecoder addr(ref); - for (auto &dim : type.dimensions()) { - auto label = addr.decodeLabel(); - if (label.size() != 0u) { - if (!first) { - out << ","; - } - out << dim.name << ":" << label; - first = false; - } - } - assert(!addr.valid()); - out << "}"; -} - } SparseTensor::SparseTensor(const eval::ValueType &type_in, @@ -98,13 +77,13 @@ SparseTensor::combineDimensionsWith(const SparseTensor &rhs) const } const eval::ValueType & -SparseTensor::getType() const +SparseTensor::type() const { return _type; } double -SparseTensor::sum() const +SparseTensor::as_double() const { double result = 0.0; for (const auto &cell : _cells) { @@ -114,84 +93,11 @@ SparseTensor::sum() const } Tensor::UP -SparseTensor::add(const Tensor &arg) const -{ - const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg); - if (!rhs) { - return Tensor::UP(); - } - return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue) - { return lhsValue + rhsValue; }); -} - -Tensor::UP -SparseTensor::subtract(const Tensor &arg) const -{ - const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg); - if (!rhs) { - return Tensor::UP(); - } - return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue) - { return lhsValue - rhsValue; }); -} - -Tensor::UP -SparseTensor::multiply(const Tensor &arg) const -{ - const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg); - if (!rhs) { - return Tensor::UP(); - } - return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue) - { return lhsValue * rhsValue; }); -} - -Tensor::UP -SparseTensor::min(const Tensor &arg) const -{ - const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg); - if (!rhs) { - return Tensor::UP(); - } - return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue) - { return std::min(lhsValue, rhsValue); }); -} - -Tensor::UP -SparseTensor::max(const Tensor &arg) const -{ - const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg); - if (!rhs) { - return Tensor::UP(); - } - return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue) - { return std::max(lhsValue, rhsValue); }); -} - -Tensor::UP -SparseTensor::match(const Tensor &arg) const -{ - const SparseTensor *rhs = dynamic_cast<const SparseTensor *>(&arg); - if (!rhs) { - return Tensor::UP(); - } - return SparseTensorMatch(*this, *rhs).result(); -} - -Tensor::UP SparseTensor::apply(const CellFunction &func) const { return TensorApply<SparseTensor>(*this, func).result(); } -Tensor::UP -SparseTensor::sum(const vespalib::string &dimension) const -{ - return sparse::reduce(*this, { dimension }, - [](double lhsValue, double rhsValue) - { return lhsValue + rhsValue; }); -} - bool SparseTensor::equals(const Tensor &arg) const { @@ -202,14 +108,6 @@ SparseTensor::equals(const Tensor &arg) const return *this == *rhs; } -vespalib::string -SparseTensor::toString() const -{ - std::ostringstream stream; - stream << *this; - return stream.str(); -} - Tensor::UP SparseTensor::clone() const { @@ -235,7 +133,7 @@ buildAddress(const eval::ValueType &type, TensorSpec SparseTensor::toSpec() const { - TensorSpec result(getType().to_spec()); + TensorSpec result(type().to_spec()); TensorSpec::Address address; for (const auto &cell : _cells) { SparseTensorAddressDecoder decoder(cell.first); @@ -250,22 +148,6 @@ SparseTensor::toSpec() const } void -SparseTensor::print(std::ostream &out) const -{ - out << "{ "; - bool first = true; - for (const auto &cell : cells()) { - if (!first) { - out << ", "; - } - printAddress(out, cell.first, _type); - out << ":" << cell.second; - first = false; - } - out << " }"; -} - -void SparseTensor::accept(TensorVisitor &visitor) const { TensorAddressBuilder addrBuilder; @@ -292,6 +174,14 @@ SparseTensor::join(join_fun_t function, const Tensor &arg) const if (!rhs) { return Tensor::UP(); } + if (function == eval::operation::Mul::f) { + if (fast_type() == rhs->fast_type()) { + return SparseTensorMatch(*this, *rhs).result(); + } else { + return sparse::apply(*this, *rhs, [](double lhsValue, double rhsValue) + { return lhsValue * rhsValue; }); + } + } return sparse::apply(*this, *rhs, function); } diff --git a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h index 8f5f8066352..c7c38f0a182 100644 --- a/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h +++ b/eval/src/vespa/eval/tensor/sparse/sparse_tensor.h @@ -41,24 +41,15 @@ public: bool operator==(const SparseTensor &rhs) const; eval::ValueType combineDimensionsWith(const SparseTensor &rhs) const; - virtual const eval::ValueType &getType() const override; - virtual double sum() const override; - virtual Tensor::UP add(const Tensor &arg) const override; - virtual Tensor::UP subtract(const Tensor &arg) const override; - virtual Tensor::UP multiply(const Tensor &arg) const override; - virtual Tensor::UP min(const Tensor &arg) const override; - virtual Tensor::UP max(const Tensor &arg) const override; - virtual Tensor::UP match(const Tensor &arg) const override; + virtual const eval::ValueType &type() const override; + virtual double as_double() const override; virtual Tensor::UP apply(const CellFunction &func) const override; - virtual Tensor::UP sum(const vespalib::string &dimension) const override; virtual Tensor::UP join(join_fun_t function, const Tensor &arg) const override; virtual Tensor::UP reduce(join_fun_t op, const std::vector<vespalib::string> &dimensions) const override; virtual bool equals(const Tensor &arg) const override; - virtual void print(std::ostream &out) const override; - virtual vespalib::string toString() const override; virtual Tensor::UP clone() const override; virtual eval::TensorSpec toSpec() const override; virtual void accept(TensorVisitor &visitor) const override; diff --git a/eval/src/vespa/eval/tensor/tensor.cpp b/eval/src/vespa/eval/tensor/tensor.cpp index bbf8dd5017c..8715a864f68 100644 --- a/eval/src/vespa/eval/tensor/tensor.cpp +++ b/eval/src/vespa/eval/tensor/tensor.cpp @@ -30,7 +30,7 @@ Tensor::supported(TypeList types) std::ostream & operator<<(std::ostream &out, const Tensor &value) { - value.print(out); + out << value.toSpec().to_string(); return out; } diff --git a/eval/src/vespa/eval/tensor/tensor.h b/eval/src/vespa/eval/tensor/tensor.h index 80afbbf52ff..8e31448e026 100644 --- a/eval/src/vespa/eval/tensor/tensor.h +++ b/eval/src/vespa/eval/tensor/tensor.h @@ -30,27 +30,11 @@ struct Tensor : public eval::Tensor Tensor(); virtual ~Tensor() {} - virtual const eval::ValueType &getType() const = 0; - virtual const eval::ValueType &type() const override { return getType(); } - virtual double sum() const = 0; - virtual double as_double() const final override { return sum(); } - virtual Tensor::UP add(const Tensor &arg) const = 0; - virtual Tensor::UP subtract(const Tensor &arg) const = 0; - virtual Tensor::UP multiply(const Tensor &arg) const = 0; - virtual Tensor::UP min(const Tensor &arg) const = 0; - virtual Tensor::UP max(const Tensor &arg) const = 0; - virtual Tensor::UP match(const Tensor &arg) const = 0; virtual Tensor::UP apply(const CellFunction &func) const = 0; - virtual Tensor::UP sum(const vespalib::string &dimension) const = 0; - virtual Tensor::UP join(join_fun_t function, - const Tensor &arg) const = 0; - virtual Tensor::UP reduce(join_fun_t op, - const std::vector<vespalib::string> &dimensions) - const = 0; - virtual bool equals(const Tensor &arg) const = 0; - virtual void print(std::ostream &out) const = 0; - virtual vespalib::string toString() const = 0; - virtual Tensor::UP clone() const = 0; + virtual Tensor::UP join(join_fun_t function, const Tensor &arg) const = 0; + virtual Tensor::UP reduce(join_fun_t op, const std::vector<vespalib::string> &dimensions) const = 0; + virtual bool equals(const Tensor &arg) const = 0; // want to remove, but needed by document + virtual Tensor::UP clone() const = 0; // want to remove, but needed by document virtual eval::TensorSpec toSpec() const = 0; virtual void accept(TensorVisitor &visitor) const = 0; diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp index 7ad97a6e84e..463105b7c1f 100644 --- a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp +++ b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp @@ -17,12 +17,6 @@ WrappedSimpleTensor::equals(const Tensor &arg) const return (lhs_spec == rhs_spec); } -vespalib::string -WrappedSimpleTensor::toString() const -{ - return toSpec().to_string(); -} - eval::TensorSpec WrappedSimpleTensor::toSpec() const { @@ -30,13 +24,9 @@ WrappedSimpleTensor::toSpec() const } double -WrappedSimpleTensor::sum() const +WrappedSimpleTensor::as_double() const { - double result = 0.0; - for (const auto &cell: _tensor.cells()) { - result += cell.value; - } - return result; + return _tensor.as_double(); } void @@ -57,12 +47,6 @@ WrappedSimpleTensor::accept(TensorVisitor &visitor) const } } -void -WrappedSimpleTensor::print(std::ostream &out) const -{ - out << toString(); -} - Tensor::UP WrappedSimpleTensor::clone() const { @@ -73,48 +57,6 @@ WrappedSimpleTensor::clone() const //----------------------------------------------------------------------------- Tensor::UP -WrappedSimpleTensor::add(const Tensor &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP -WrappedSimpleTensor::subtract(const Tensor &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP -WrappedSimpleTensor::multiply(const Tensor &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP -WrappedSimpleTensor::min(const Tensor &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP -WrappedSimpleTensor::max(const Tensor &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP -WrappedSimpleTensor::match(const Tensor &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP WrappedSimpleTensor::apply(const CellFunction &) const { abort(); @@ -122,13 +64,6 @@ WrappedSimpleTensor::apply(const CellFunction &) const } Tensor::UP -WrappedSimpleTensor::sum(const vespalib::string &) const -{ - abort(); - return Tensor::UP(); -} - -Tensor::UP WrappedSimpleTensor::join(join_fun_t, const Tensor &) const { abort(); diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h index ef3cb6425c1..ae7907845e1 100644 --- a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h +++ b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h @@ -28,23 +28,14 @@ public: : _space(std::move(tensor)), _tensor(*_space) {} ~WrappedSimpleTensor() {} const eval::SimpleTensor &get() const { return _tensor; } - const eval::ValueType &getType() const override { return _tensor.type(); } + const eval::ValueType &type() const override { return _tensor.type(); } bool equals(const Tensor &arg) const override; - vespalib::string toString() const override; eval::TensorSpec toSpec() const override; - double sum() const override; + double as_double() const override; void accept(TensorVisitor &visitor) const override; - void print(std::ostream &out) const override; Tensor::UP clone() const override; // functions below should not be used for this implementation - Tensor::UP add(const Tensor &) const override; - Tensor::UP subtract(const Tensor &) const override; - Tensor::UP multiply(const Tensor &) const override; - Tensor::UP min(const Tensor &) const override; - Tensor::UP max(const Tensor &) const override; - Tensor::UP match(const Tensor &) const override; Tensor::UP apply(const CellFunction &) const override; - Tensor::UP sum(const vespalib::string &) const override; Tensor::UP join(join_fun_t, const Tensor &) const override; Tensor::UP reduce(join_fun_t, const std::vector<vespalib::string> &) const override; }; diff --git a/fileacquirer/pom.xml b/fileacquirer/pom.xml index eb040eddffb..6449b8c620f 100644 --- a/fileacquirer/pom.xml +++ b/fileacquirer/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>fileacquirer</artifactId> <version>6-SNAPSHOT</version> diff --git a/filedistribution/OWNERS b/filedistribution/OWNERS index 31af040f698..2faf7df0593 100644 --- a/filedistribution/OWNERS +++ b/filedistribution/OWNERS @@ -1 +1,2 @@ -bratseth +baldersheim +hmusum diff --git a/filedistribution/pom.xml b/filedistribution/pom.xml index 41622792e43..d9699b700d0 100644 --- a/filedistribution/pom.xml +++ b/filedistribution/pom.xml @@ -9,7 +9,8 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> - </parent> + <relativePath>../parent/pom.xml</relativePath> + </parent> <artifactId>filedistribution</artifactId> <version>6-SNAPSHOT</version> @@ -19,11 +20,6 @@ <dependencies> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>config-lib</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> <artifactId>vespajlib</artifactId> <version>${project.version}</version> </dependency> @@ -58,6 +54,10 @@ <artifactId>junit</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + </dependency> </dependencies> <build> diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java new file mode 100644 index 00000000000..2c08f5a7605 --- /dev/null +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/CompressedFileReference.java @@ -0,0 +1,118 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.filedistribution; + +import com.google.common.io.ByteStreams; +import com.yahoo.log.LogLevel; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Utility class for compressing and decompressing files used in a file reference + * + * @author hmusum + */ +public class CompressedFileReference { + + private static final Logger log = Logger.getLogger(CompressedFileReference.class.getName()); + private static final int recurseDepth = 100; + + public static File compress(File baseDir, List<File> inputFiles, File outputFile) throws IOException { + ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(outputFile))); + createArchiveFile(archiveOutputStream, baseDir, inputFiles); + return outputFile; + } + + public static File compress(File directory, File outputFile) throws IOException { + return compress(directory, Files.find(Paths.get(directory.getAbsolutePath()), + recurseDepth, + (p, basicFileAttributes) -> basicFileAttributes.isRegularFile()) + .map(Path::toFile).collect(Collectors.toList()), outputFile); + } + + public static byte[] compress(File directory) throws IOException { + return compress(directory, Files.find(Paths.get(directory.getAbsolutePath()), + recurseDepth, + (p, basicFileAttributes) -> basicFileAttributes.isRegularFile()) + .map(Path::toFile).collect(Collectors.toList())); + } + + public static byte[] compress(File baseDir, List<File> inputFiles) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(out)); + createArchiveFile(archiveOutputStream, baseDir, inputFiles); + return out.toByteArray(); + } + + static void decompress(File inputFile, File outputDir) throws IOException { + log.log(LogLevel.DEBUG, "Decompressing '" + inputFile + "' into '" + outputDir + "'"); + ArchiveInputStream ais = new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(inputFile))); + decompress(ais, outputDir); + ais.close(); + } + + private static void decompress(ArchiveInputStream archiveInputStream, File outputFile) throws IOException { + int entries = 0; + ArchiveEntry entry; + while ((entry = archiveInputStream.getNextEntry()) != null) { + log.log(LogLevel.DEBUG, "Unpacking " + entry.getName()); + File outFile = new File(outputFile, entry.getName()); + if (entry.isDirectory()) { + if (!(outFile.exists() && outFile.isDirectory())) { + log.log(LogLevel.DEBUG, "Creating dir: " + outFile.getAbsolutePath()); + if (!outFile.mkdirs()) { + log.log(LogLevel.WARNING, "Could not create dir " + entry.getName()); + } + } + } else { + // Create parent dir if necessary + File parent = new File(outFile.getParent()); + if (!parent.exists() && !parent.mkdirs()) { + log.log(LogLevel.WARNING, "Could not create dir " + parent.getAbsolutePath()); + } + FileOutputStream fos = new FileOutputStream(outFile); + ByteStreams.copy(archiveInputStream, fos); + fos.close(); + } + entries++; + } + if (entries == 0) { + log.log(LogLevel.WARNING, "Not able to read any entries from " + outputFile.getName()); + } + } + + private static void createArchiveFile(ArchiveOutputStream archiveOutputStream, File baseDir, List<File> inputFiles) throws IOException { + inputFiles.forEach(file -> { + try { + writeFileToTar(archiveOutputStream, baseDir, file); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + archiveOutputStream.close(); + } + + private static void writeFileToTar(ArchiveOutputStream taos, File baseDir, File file) throws IOException { + log.log(LogLevel.DEBUG, "Adding file to tar: " + baseDir.toPath().relativize(file.toPath()).toString()); + taos.putArchiveEntry(taos.createArchiveEntry(file, baseDir.toPath().relativize(file.toPath()).toString())); + ByteStreams.copy(new FileInputStream(file), taos); + taos.closeArchiveEntry(); + } +} + diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java index d09cf17b9e3..2e58455bc39 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDistributionRpcServer.java @@ -118,7 +118,7 @@ public class FileDistributionRpcServer { List<FileReference> fileReferences = Stream.of(fileReferenceStrings) .map(FileReference::new) .collect(Collectors.toList()); - downloader.queueForDownload(fileReferences); + downloader.queueForAsyncDownload(fileReferences); req.returnValues().add(new Int32Value(0)); } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java index cd4b3afb9b5..727786cdc78 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileDownloader.java @@ -1,13 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.filedistribution; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.yahoo.config.FileReference; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.ConnectionPool; import com.yahoo.vespa.defaults.Defaults; -import com.yahoo.yolean.Exceptions; import java.io.File; import java.time.Duration; @@ -16,8 +14,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Logger; @@ -37,37 +35,55 @@ public class FileDownloader { public FileDownloader(ConnectionPool connectionPool) { this(connectionPool, - new File(Defaults.getDefaults().underVespaHome("var/db/vespa/filedistribution")), - Duration.ofMinutes(15)); + new File(Defaults.getDefaults().underVespaHome("var/db/vespa/filedistribution")), + new File(Defaults.getDefaults().underVespaHome("tmp")), + Duration.ofMinutes(15)); } - FileDownloader(ConnectionPool connectionPool, File downloadDirectory, Duration timeout) { + FileDownloader(ConnectionPool connectionPool, File downloadDirectory, File tmpDirectory, Duration timeout) { this.downloadDirectory = downloadDirectory; this.timeout = timeout; - this.fileReferenceDownloader = new FileReferenceDownloader(downloadDirectory, connectionPool, timeout); + this.fileReferenceDownloader = new FileReferenceDownloader(downloadDirectory, tmpDirectory, connectionPool, timeout); } public Optional<File> getFile(FileReference fileReference) { + try { + return getFutureFile(fileReference).get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + return Optional.empty(); + } + } + + private Future<Optional<File>> getFutureFile(FileReference fileReference) { Objects.requireNonNull(fileReference, "file reference cannot be null"); File directory = new File(downloadDirectory, fileReference.value()); log.log(LogLevel.DEBUG, "Checking if there is a file in '" + directory.getAbsolutePath() + "' "); Optional<File> file = getFileFromFileSystem(fileReference, directory); if (file.isPresent()) { - return file; + SettableFuture<Optional<File>> future = SettableFuture.create(); + future.set(file); + return future; } else { log.log(LogLevel.INFO, "File reference '" + fileReference.value() + "' not found in " + directory.getAbsolutePath() + ", starting download"); - return queueForDownload(fileReference, timeout); + return queueForAsyncDownload(fileReference, timeout); } } - public void queueForDownload(List<FileReference> fileReferences) { - fileReferences.forEach(this::queueForDownload); + // Start downloading, but there is no Future used get file being downloaded + public void queueForAsyncDownload(List<FileReference> fileReferences) { + fileReferences.forEach(fileReference -> { + if (fileReferenceDownloader.isDownloading(fileReference)) { + log.log(LogLevel.DEBUG, "Already downloading '" + fileReference.value() + "'"); + } else { + queueForAsyncDownload(fileReference); + } + }); } - public void receiveFile(FileReference fileReference, String filename, byte[] content, long xxHash) { - fileReferenceDownloader.receiveFile(fileReference, filename, content, xxHash); + void receiveFile(FileReferenceData fileReferenceData) { + fileReferenceDownloader.receiveFile(fileReferenceData); } double downloadStatus(FileReference fileReference) { @@ -85,17 +101,11 @@ public class FileDownloader { private Optional<File> getFileFromFileSystem(FileReference fileReference, File directory) { File[] files = directory.listFiles(); if (directory.exists() && directory.isDirectory() && files != null && files.length > 0) { - if (files.length != 1) { - throw new RuntimeException("More than one file in '" + fileReference.value() + - "', expected only one, unable to proceed"); - } File file = files[0]; if (!file.exists()) { - throw new RuntimeException("File with reference '" + fileReference.value() + - "' does not exist"); + throw new RuntimeException("File with reference '" + fileReference.value() + "' does not exist"); } else if (!file.canRead()) { - throw new RuntimeException("File with reference '" + fileReference.value() + - "'exists, but unable to read it"); + throw new RuntimeException("File with reference '" + fileReference.value() + "'exists, but unable to read it"); } else { fileReferenceDownloader.setDownloadStatus(fileReference.value(), 100.0); return Optional.of(file); @@ -104,50 +114,25 @@ public class FileDownloader { return Optional.empty(); } - private synchronized Optional<File> queueForDownload(FileReference fileReference, Duration timeout) { - if (fileReferenceDownloader.isDownloading(fileReference)) { - log.log(LogLevel.INFO, "Already downloading '" + fileReference.value() + "'"); - ListenableFuture<Optional<File>> future = - fileReferenceDownloader.addDownloadListener(fileReference, () -> getFile(fileReference)); - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException("Failed downloading file reference '" + fileReference.value() + "': " + - Exceptions.toMessageString(e)); - } + private synchronized Future<Optional<File>> queueForAsyncDownload(FileReference fileReference, Duration timeout) { + Future<Optional<File>> inProgress = fileReferenceDownloader.addDownloadListener(fileReference, () -> getFile(fileReference)); + if (inProgress != null) { + log.log(LogLevel.DEBUG, "Already downloading '" + fileReference.value() + "'"); + return inProgress; } - SettableFuture<Optional<File>> future = SettableFuture.create(); - queueForDownload(new FileReferenceDownload(fileReference, future)); + Future<Optional<File>> future = queueForAsyncDownload(fileReference); log.log(LogLevel.INFO, "Queued '" + fileReference.value() + "' for download with timeout " + timeout); - - try { - Optional<File> fileDownloaded; - try { - log.log(LogLevel.INFO, "Waiting for '" + fileReference.value() + "' to download"); - fileDownloaded = future.get(timeout.getSeconds() - 1, TimeUnit.SECONDS); - log.log(LogLevel.INFO, "'" + fileReference.value() + "' downloaded"); - } catch (TimeoutException e) { - log.log(LogLevel.WARNING, "Downloading '" + fileReference.value() + "' timed out"); - return Optional.empty(); - } - return fileDownloaded; - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException("Could not download '" + fileReference.value() + "'"); - } + return future; } - // We don't care about the future in this call - private synchronized void queueForDownload(FileReference fileReference) { - queueForDownload(new FileReferenceDownload(fileReference, SettableFuture.create())); - } - - private synchronized void queueForDownload(FileReferenceDownload fileReferenceDownload) { + private Future<Optional<File>> queueForAsyncDownload(FileReference fileReference) { + FileReferenceDownload fileReferenceDownload = new FileReferenceDownload(fileReference, SettableFuture.create()); fileReferenceDownloader.addToDownloadQueue(fileReferenceDownload); + return fileReferenceDownload.future(); } - Set<FileReference> queuedDownloads() { - return fileReferenceDownloader.queuedDownloads(); + public FileReferenceDownloader fileReferenceDownloader() { + return fileReferenceDownloader; } - } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java index 3999389d2c7..d57ce4ca5de 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.filedistribution; @@ -8,49 +8,157 @@ import com.yahoo.jrt.Method; import com.yahoo.jrt.Request; import com.yahoo.jrt.Supervisor; import com.yahoo.log.LogLevel; -import net.jpountz.xxhash.XXHash64; +import net.jpountz.xxhash.StreamingXXHash64; import net.jpountz.xxhash.XXHashFactory; import java.io.File; import java.io.IOException; -import java.nio.ByteBuffer; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; +/** + * When asking for a file reference, this handles RPC callbacks from config server with file data and metadata. + * Uses the same Supervisor as the original caller that requests files, so communication uses the same + * connection in both directions. + * + * @author baldersheim + */ public class FileReceiver { private final static Logger log = Logger.getLogger(FileReceiver.class.getName()); - private final static String RECEIVE_METHOD = "filedistribution.receiveFile"; - private final static String RECEIVE_META_METHOD = "filedistribution.receiveFileMeta"; - private final static String RECEIVE_PART_METHOD = "filedistribution.receiveFilePart"; - private final static String RECEIVE_EOF_METHOD = "filedistribution.receiveFileEof"; + public final static String RECEIVE_METHOD = "filedistribution.receiveFile"; + public final static String RECEIVE_META_METHOD = "filedistribution.receiveFileMeta"; + public final static String RECEIVE_PART_METHOD = "filedistribution.receiveFilePart"; + public final static String RECEIVE_EOF_METHOD = "filedistribution.receiveFileEof"; private final Supervisor supervisor; private final FileReferenceDownloader downloader; private final File downloadDirectory; - private final XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); + // Should be on same partition as downloadDirectory to make sure moving files from tmpDirectory + // to downloadDirectory is atomic + private final File tmpDirectory; + private final AtomicInteger nextSessionId = new AtomicInteger(1); + private final Map<Integer, Session> sessions = new HashMap<>(); - public FileReceiver(Supervisor supervisor, FileReferenceDownloader downloader, File downloadDirectory) { + final static class Session { + private final StreamingXXHash64 hasher; + private final int sessionId; + private final FileReference reference; + private final FileReferenceData.Type fileType; + private final String fileName; + private final long fileSize; + private long currentFileSize; + private long currentPartId; + private long currentHash; + private final File fileReferenceDir; + private final File tmpDir; + private final File inprogressFile; + + Session(File downloadDirectory, File tmpDirectory, int sessionId, FileReference reference, + FileReferenceData.Type fileType, String fileName, long fileSize) + { + this.hasher = XXHashFactory.fastestInstance().newStreamingHash64(0); + this.sessionId = sessionId; + this.reference = reference; + this.fileType = fileType; + this.fileName = fileName; + this.fileSize = fileSize; + currentFileSize = 0; + currentPartId = 0; + currentHash = 0; + fileReferenceDir = new File(downloadDirectory, reference.value()); + this.tmpDir = tmpDirectory; + try { + Files.createDirectories(fileReferenceDir.toPath()); + } catch (IOException e) { + log.log(LogLevel.ERROR, "Failed creating directory(" + fileReferenceDir.toPath() + "): " + e.getMessage(), e); + throw new RuntimeException("Failed creating directory(" + fileReferenceDir.toPath() + "): ", e); + } + + try { + inprogressFile = Files.createTempFile(tmpDirectory.toPath(), fileName, ".inprogress").toFile(); + } catch (IOException e) { + String msg = "Failed creating tempfile for inprogress file for(" + fileName + ") in '" + fileReferenceDir.toPath() + "': "; + log.log(LogLevel.ERROR, msg + e.getMessage(), e); + throw new RuntimeException(msg, e); + } + } + + void addPart(int partId, byte [] part) { + if (partId != currentPartId) { + throw new IllegalStateException("Received partid " + partId + " while expecting " + currentPartId); + } + if (fileSize < currentFileSize + part.length) { + throw new IllegalStateException("Received part would extend the file from " + currentFileSize + " to " + + (currentFileSize + part.length) + ", but " + fileSize + " is max."); + } + try { + Files.write(inprogressFile.toPath(), part, StandardOpenOption.WRITE, StandardOpenOption.APPEND); + } catch (IOException e) { + log.log(LogLevel.ERROR, "Failed writing to file(" + inprogressFile.toPath() + "): " + e.getMessage(), e); + throw new RuntimeException("Failed writing to file(" + inprogressFile.toPath() + "): ", e); + } + currentFileSize += part.length; + currentPartId++; + hasher.update(part, 0, part.length); + } + + File close(long hash) { + if (hasher.getValue() != hash) { + throw new RuntimeException("xxhash from content (" + currentHash + ") is not equal to xxhash in request (" + hash + ")"); + } + File file = new File(fileReferenceDir, fileName); + try { + // Unpack if necessary + if (fileType == FileReferenceData.Type.compressed) { + File decompressedDir = Files.createTempDirectory(tmpDir.toPath(), "archive").toFile(); + log.log(LogLevel.DEBUG, "Archived file, unpacking " + inprogressFile + " to " + decompressedDir); + CompressedFileReference.decompress(inprogressFile, decompressedDir); + moveFileToDestination(decompressedDir, fileReferenceDir); + } else { + log.log(LogLevel.DEBUG, "Uncompressed file, moving to " + file.getAbsolutePath()); + moveFileToDestination(inprogressFile, file); + } + } catch (IOException e) { + log.log(LogLevel.ERROR, "Failed writing file: " + e.getMessage(), e); + throw new RuntimeException("Failed writing file: ", e); + } + return file; + } + + double percentageReceived() { + return (double)currentFileSize/(double)fileSize; + } + } + + FileReceiver(Supervisor supervisor, FileReferenceDownloader downloader, File downloadDirectory, File tmpDirectory) { this.supervisor = supervisor; this.downloader = downloader; this.downloadDirectory = downloadDirectory; + this.tmpDirectory = tmpDirectory; registerMethods(); } private void registerMethods() { - receiveFileMethod(this).forEach((method) -> supervisor.addMethod(method)); + receiveFileMethod(this).forEach(supervisor::addMethod); } // Defined here so that it can be added to supervisor used by client (server will use same connection when calling // receiveFile after getting a serveFile method call). handler needs to implement receiveFile method private List<Method> receiveFileMethod(Object handler) { List<Method> methods = new ArrayList<>(); - methods.add(new Method(RECEIVE_META_METHOD, "ssl", "ii", handler,"receiveFileMeta") + methods.add(new Method(RECEIVE_META_METHOD, "sssl", "ii", handler,"receiveFileMeta") .paramDesc(0, "filereference", "file reference to download") .paramDesc(1, "filename", "filename") - .paramDesc(2, "filelength", "length in bytes of file") + .paramDesc(2, "type", "'file' or 'compressed'") + .paramDesc(3, "filelength", "length in bytes of file") .returnDesc(0, "ret", "0 if success, 1 otherwise") .returnDesc(1, "session-id", "Session id to be used for this transfer")); methods.add(new Method(RECEIVE_PART_METHOD, "siix", "i", handler,"receiveFilePart") @@ -67,14 +175,15 @@ public class FileReceiver { .paramDesc(4, "error-description", "Error description.") .returnDesc(0, "ret", "0 if success, 1 if crc mismatch, 2 otherwise")); // Temporary method until we have chunking - methods.add(new Method(RECEIVE_METHOD, "ssxlis", "i", handler, "receiveFile") + methods.add(new Method(RECEIVE_METHOD, "sssxlis", "i", handler, "receiveFile") .methodDesc("receive file reference content") .paramDesc(0, "file reference", "file reference to download") .paramDesc(1, "filename", "filename") - .paramDesc(2, "content", "array of bytes") - .paramDesc(3, "hash", "xx64hash of the file content") - .paramDesc(4, "errorcode", "Error code. 0 if none") - .paramDesc(5, "error-description", "Error description.") + .paramDesc(2, "type", "'file' or 'compressed'") + .paramDesc(3, "content", "array of bytes") + .paramDesc(4, "hash", "xx64hash of the file content") + .paramDesc(5, "errorcode", "Error code. 0 if none") + .paramDesc(6, "error-description", "Error description.") .returnDesc(0, "ret", "0 if success, 1 otherwise")); return methods; } @@ -83,15 +192,15 @@ public class FileReceiver { public final void receiveFile(Request req) { FileReference fileReference = new FileReference(req.parameters().get(0).asString()); String filename = req.parameters().get(1).asString(); - byte[] content = req.parameters().get(2).asData(); - long xxhash = req.parameters().get(3).asInt64(); - int errorCode = req.parameters().get(4).asInt32(); - String errorDescription = req.parameters().get(5).asString(); + String type = req.parameters().get(2).asString(); + byte[] content = req.parameters().get(3).asData(); + long xxhash = req.parameters().get(4).asInt64(); + int errorCode = req.parameters().get(5).asInt32(); + String errorDescription = req.parameters().get(6).asString(); if (errorCode == 0) { - // TODO: Remove when system test works - log.log(LogLevel.INFO, "Receiving file reference '" + fileReference.value() + "'"); - receiveFile(fileReference, filename, content, xxhash); + log.log(LogLevel.DEBUG, "Receiving file reference '" + fileReference.value() + "'"); + receiveFile(new FileReferenceDataBlob(fileReference, filename, FileReferenceData.Type.valueOf(type), content, xxhash)); req.returnValues().add(new Int32Value(0)); } else { log.log(LogLevel.WARNING, "Receiving file reference '" + fileReference.value() + "' failed: " + errorDescription); @@ -100,34 +209,131 @@ public class FileReceiver { } } - void receiveFile(FileReference fileReference, String filename, byte[] content, long xxHash) { - long xxHashFromContent = hasher.hash(ByteBuffer.wrap(content), 0); - if (xxHashFromContent != xxHash) - throw new RuntimeException("xxhash from content (" + xxHashFromContent + ") is not equal to xxhash in request (" + xxHash + ")"); + void receiveFile(FileReferenceData fileReferenceData) { + long xxHashFromContent = fileReferenceData.xxhash(); + if (xxHashFromContent != fileReferenceData.xxhash()) { + throw new RuntimeException("xxhash from content (" + xxHashFromContent + ") is not equal to xxhash in request (" + fileReferenceData.xxhash() + ")"); + } - File fileReferenceDir = new File(downloadDirectory, fileReference.value()); + File fileReferenceDir = new File(downloadDirectory, fileReferenceData.fileReference().value()); + // file might be a directory (and then type is compressed) + File file = new File(fileReferenceDir, fileReferenceData.filename()); try { - Files.createDirectories(fileReferenceDir.toPath()); - File file = new File(fileReferenceDir, filename); - log.log(LogLevel.INFO, "Writing data to " + file.getAbsolutePath()); - Files.write(file.toPath(), content); - downloader.completedDownloading(fileReference, file); + File tempDownloadedDir = Files.createTempDirectory(tmpDirectory.toPath(), "downloaded").toFile(); + File tempFile = new File(tempDownloadedDir, fileReferenceData.filename()); + Files.write(tempFile.toPath(), fileReferenceData.content().array()); + + // Unpack if necessary + if (fileReferenceData.type() == FileReferenceData.Type.compressed) { + File decompressedDir = Files.createTempDirectory(tempDownloadedDir.toPath(), "decompressed").toFile(); + log.log(LogLevel.DEBUG, "Compressed file, unpacking " + tempFile + " to " + decompressedDir); + CompressedFileReference.decompress(tempFile, decompressedDir); + moveFileToDestination(decompressedDir, fileReferenceDir); + } else { + log.log(LogLevel.DEBUG, "Uncompressed file, moving to " + file.getAbsolutePath()); + Files.createDirectories(fileReferenceDir.toPath()); + moveFileToDestination(tempFile, file); + } + downloader.completedDownloading(fileReferenceData.fileReference(), file); } catch (IOException e) { - log.log(LogLevel.ERROR, "Failed writing file: " + e.getMessage()); + log.log(LogLevel.ERROR, "Failed writing file: " + e.getMessage(), e); throw new RuntimeException("Failed writing file: ", e); } } + private static void moveFileToDestination(File tempFile, File destination) { + try { + Files.move(tempFile.toPath(), destination.toPath()); + log.log(LogLevel.DEBUG, "File moved from " + tempFile.getAbsolutePath()+ " to " + destination.getAbsolutePath()); + } catch (FileAlreadyExistsException e) { + // Don't fail if it already exists (we might get the file from several config servers when retrying, servers are down etc. + // so it might be written already) + log.log(LogLevel.DEBUG, "File '" + destination.getAbsolutePath() + "' already exists, continuing: " + e.getMessage()); + } catch (IOException e) { + String message = "Failed moving file '" + tempFile.getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'"; + log.log(LogLevel.ERROR, message, e); + throw new RuntimeException(message, e); + } + } + @SuppressWarnings({"UnusedDeclaration"}) public final void receiveFileMeta(Request req) { - log.info("Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); + log.log(LogLevel.DEBUG, "Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); + FileReference reference = new FileReference(req.parameters().get(0).asString()); + String fileName = req.parameters().get(1).asString(); + String type = req.parameters().get(2).asString(); + long fileSize = req.parameters().get(3).asInt64(); + int sessionId = nextSessionId.getAndIncrement(); + int retval = 0; + synchronized (sessions) { + if (sessions.containsKey(sessionId)) { + retval = 1; + log.severe("Session id " + sessionId + " already exist, impossible. Request from(" + req.target() + ")"); + } else { + try { + sessions.put(sessionId, new Session(downloadDirectory, tmpDirectory, sessionId, reference, + FileReferenceData.Type.valueOf(type),fileName, fileSize)); + } catch (Exception e) { + retval = 1; + } + } + } + req.returnValues().add(new Int32Value(retval)); + req.returnValues().add(new Int32Value(sessionId)); } + @SuppressWarnings({"UnusedDeclaration"}) public final void receiveFilePart(Request req) { - log.info("Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); + log.log(LogLevel.DEBUG, "Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); + + FileReference reference = new FileReference(req.parameters().get(0).asString()); + int sessionId = req.parameters().get(1).asInt32(); + int partId = req.parameters().get(2).asInt32(); + byte [] part = req.parameters().get(3).asData(); + Session session = getSession(sessionId); + int retval = verifySession(session, sessionId, reference); + try { + session.addPart(partId, part); + } catch (Exception e) { + log.severe("Got exception + " + e); + retval = 1; + } + double completeness = (double) session.currentFileSize / (double) session.fileSize; + log.log(LogLevel.DEBUG, String.format("%.1f percent of '%s' downloaded", completeness * 100, reference.value())); + downloader.setDownloadStatus(reference, completeness); + req.returnValues().add(new Int32Value(retval)); } + @SuppressWarnings({"UnusedDeclaration"}) public final void receiveFileEof(Request req) { - log.info("Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); + log.log(LogLevel.DEBUG, "Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); + FileReference reference = new FileReference(req.parameters().get(0).asString()); + int sessionId = req.parameters().get(1).asInt32(); + long xxhash = req.parameters().get(2).asInt64(); + Session session = getSession(sessionId); + int retval = verifySession(session, sessionId, reference); + File file = session.close(xxhash); + downloader.completedDownloading(reference, file); + synchronized (sessions) { + sessions.remove(sessionId); + } + req.returnValues().add(new Int32Value(retval)); + } + + private Session getSession(Integer sessionId) { + synchronized (sessions) { + return sessions.get(sessionId); + } + } + private static int verifySession(Session session, int sessionId, FileReference reference) { + if (session == null) { + log.severe("session-id " + sessionId + " does not exist."); + return 1; + } + if (! session.reference.equals(reference)) { + log.severe("Session " + session.sessionId + " expects reference " + reference.value() + ", but was " + session.reference.value()); + return 1; + } + return 0; } } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java new file mode 100644 index 00000000000..dabdba2bfc0 --- /dev/null +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceData.java @@ -0,0 +1,67 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.filedistribution; + +import com.yahoo.config.FileReference; + +import java.nio.ByteBuffer; + + +/** + * Utility class for a file reference with data and metadata + * + * @author hmusum + */ +public abstract class FileReferenceData { + + public enum Type {file, compressed} + + private final FileReference fileReference; + private final String filename; + private final Type type; + + public FileReferenceData(FileReference fileReference, String filename, Type type) { + this.fileReference = fileReference; + this.filename = filename; + this.type = type; + } + + public FileReference fileReference() { + return fileReference; + } + + public String filename() { + return filename; + } + + public Type type() { + return type; + } + + public ByteBuffer content() { + ByteBuffer bb = ByteBuffer.allocate((int)size()); + while (bb.remaining() > 0) { + nextContent(bb); + } + return bb; + } + /** + * Will provide the next part of the content. + * + * @param bb with some available space + * @return Number of bytes transferred. + */ + public abstract int nextContent(ByteBuffer bb); + + /** + * Only guaranteed to be valid after all content has been consumed. + * @return xx64hash of content + */ + public abstract long xxhash(); + + /** + * The size of the content in bytes + * + * @return number of bytes + */ + public abstract long size(); +} diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java new file mode 100644 index 00000000000..3759cbe2ef7 --- /dev/null +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDataBlob.java @@ -0,0 +1,44 @@ +package com.yahoo.vespa.filedistribution; + +import com.yahoo.config.FileReference; +import net.jpountz.xxhash.XXHashFactory; + +import java.nio.ByteBuffer; + +public class FileReferenceDataBlob extends FileReferenceData { + private final byte[] content; + private final long xxhash; + + public FileReferenceDataBlob(FileReference fileReference, String filename, Type type, byte[] content) { + this(fileReference, filename, type, content, XXHashFactory.fastestInstance().hash64().hash(ByteBuffer.wrap(content), 0)); + } + + public FileReferenceDataBlob(FileReference fileReference, String filename, Type type, byte[] content, long xxhash) { + super(fileReference, filename, type); + this.content = content; + this.xxhash = xxhash; + } + + public static FileReferenceData empty(FileReference fileReference, String filename) { + return new FileReferenceDataBlob(fileReference, filename, FileReferenceData.Type.file, new byte[0], 0); + } + + public ByteBuffer content() { + return ByteBuffer.wrap(content); + } + @Override + public int nextContent(ByteBuffer bb) { + bb.put(content); + return content.length; + } + + @Override + public long xxhash() { + return xxhash; + } + + @Override + public long size() { + return content.length; + } +} diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java index fb511411128..048287f0892 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownload.java @@ -8,7 +8,7 @@ import com.yahoo.config.FileReference; import java.io.File; import java.util.Optional; -public class FileReferenceDownload { +class FileReferenceDownload { private final FileReference fileReference; private final SettableFuture<Optional<File>> future; @@ -20,9 +20,7 @@ public class FileReferenceDownload { FileReference fileReference() { return fileReference; } - SettableFuture<Optional<File>> future() { return future; } - } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java index 08595662f36..509231ba7ff 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java @@ -16,18 +16,11 @@ import java.io.File; import java.time.Duration; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * Downloads file reference using rpc requests to config server and keeps track of files being downloaded @@ -37,90 +30,75 @@ import java.util.stream.Collectors; * @author hmusum */ // TODO: Handle shutdown of executors -class FileReferenceDownloader { +public class FileReferenceDownloader { private final static Logger log = Logger.getLogger(FileReferenceDownloader.class.getName()); private final static Duration rpcTimeout = Duration.ofSeconds(10); private final ExecutorService downloadExecutor = Executors.newFixedThreadPool(10, new DaemonThreadFactory("filereference downloader")); - private ExecutorService readFromQueueExecutor = - Executors.newFixedThreadPool(1, new DaemonThreadFactory("filereference download queue")); private final ConnectionPool connectionPool; - private final ConcurrentLinkedQueue<FileReferenceDownload> downloadQueue = new ConcurrentLinkedQueue<>(); private final Map<FileReference, FileReferenceDownload> downloads = new LinkedHashMap<>(); private final Map<FileReference, Double> downloadStatus = new HashMap<>(); private final Duration downloadTimeout; private final FileReceiver fileReceiver; - FileReferenceDownloader(File downloadDirectory, ConnectionPool connectionPool, Duration timeout) { + FileReferenceDownloader(File downloadDirectory, File tmpDirectory, ConnectionPool connectionPool, Duration timeout) { this.connectionPool = connectionPool; this.downloadTimeout = timeout; - readFromQueueExecutor.submit(this::readFromQueue); - this.fileReceiver = new FileReceiver(connectionPool.getSupervisor(), this, downloadDirectory); + this.fileReceiver = new FileReceiver(connectionPool.getSupervisor(), this, downloadDirectory, tmpDirectory); } - private synchronized Optional<File> startDownload(FileReference fileReference, - Duration timeout, - FileReferenceDownload fileReferenceDownload) - throws ExecutionException, InterruptedException, TimeoutException { - downloads.put(fileReference, fileReferenceDownload); - setDownloadStatus(fileReference.value(), 0.0); - - int numAttempts = 0; + private void startDownload(Duration timeout, FileReferenceDownload fileReferenceDownload) { + FileReference fileReference = fileReferenceDownload.fileReference(); + synchronized (downloads) { + downloads.put(fileReference, fileReferenceDownload); + downloadStatus.put(fileReference, 0.0); + } + long end = System.currentTimeMillis() + timeout.toMillis(); boolean downloadStarted = false; - do { - if (startDownloadRpc(fileReference)) - downloadStarted = true; - else - Thread.sleep(100); - } while (!downloadStarted && ++numAttempts <= 10); // TODO: How long/many times to retry? - - if (downloadStarted) { - return fileReferenceDownload.future().get(timeout.toMillis(), TimeUnit.MILLISECONDS); - } else { - fileReferenceDownload.future().setException(new RuntimeException("Failed getting file reference '" + fileReference.value() + "'")); - downloads.remove(fileReference); - return Optional.empty(); + while ((System.currentTimeMillis() < end) && !downloadStarted) { + try { + if (startDownloadRpc(fileReference)) { + downloadStarted = true; + } else { + Thread.sleep(10); + } + } + catch (InterruptedException e) {} } - } - synchronized void addToDownloadQueue(FileReferenceDownload fileReferenceDownload) { - downloadQueue.add(fileReferenceDownload); + if ( !downloadStarted) { + fileReferenceDownload.future().setException(new RuntimeException("Failed getting file reference '" + fileReference.value() + "'")); + synchronized (downloads) { + downloads.remove(fileReference); + } + } } - void receiveFile(FileReference fileReference, String filename, byte[] content, long xxHash) { - fileReceiver.receiveFile(fileReference, filename, content, xxHash); + void addToDownloadQueue(FileReferenceDownload fileReferenceDownload) { + log.log(LogLevel.DEBUG, "Will download file reference '" + fileReferenceDownload.fileReference().value() + "' with timeout " + downloadTimeout); + downloadExecutor.submit(() -> startDownload(downloadTimeout, fileReferenceDownload)); } - synchronized Set<FileReference> queuedDownloads() { - return downloadQueue.stream() - .map(FileReferenceDownload::fileReference) - .collect(Collectors.toCollection(LinkedHashSet::new)); + void receiveFile(FileReferenceData fileReferenceData) { + fileReceiver.receiveFile(fileReferenceData); } - private void readFromQueue() { - do { - FileReferenceDownload fileReferenceDownload = downloadQueue.poll(); - if (fileReferenceDownload == null) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { /* ignore for now */} + void completedDownloading(FileReference fileReference, File file) { + synchronized (downloads) { + FileReferenceDownload download = downloads.get(fileReference); + if (download != null) { + downloadStatus.put(fileReference, 1.0); + downloads.remove(fileReference); + download.future().set(Optional.of(file)); } else { - log.log(LogLevel.INFO, "Polling queue, found file reference '" + - fileReferenceDownload.fileReference().value() + "' to download"); - downloadExecutor.submit(() -> startDownload(fileReferenceDownload.fileReference(), downloadTimeout, fileReferenceDownload)); + log.log(LogLevel.WARNING, "Received a file " + fileReference + " I did not ask for. Impossible"); } - } while (true); - } - - void completedDownloading(FileReference fileReference, File file) { - if (downloads.containsKey(fileReference)) - downloads.get(fileReference).future().set(Optional.of(file)); - downloadStatus.put(fileReference, 100.0); + } } - private boolean startDownloadRpc(FileReference fileReference) throws ExecutionException, InterruptedException { + private boolean startDownloadRpc(FileReference fileReference) { Connection connection = connectionPool.getCurrent(); Request request = new Request("filedistribution.serveFile"); request.parameters().add(new StringValue(fileReference.value())); @@ -133,24 +111,35 @@ class FileReferenceDownloader { return true; } else { log.log(LogLevel.INFO, "File reference '" + fileReference.value() + "' not found for " + connection.getAddress()); + connectionPool.setNewCurrentConnection(); return false; } } else { - log.log(LogLevel.WARNING, "Request failed. Req: " + request + "\nSpec: " + connection.getAddress()); - if (request.isError() && request.errorCode() == ErrorCode.CONNECTION) - connection.setError(request.errorCode()); + log.log(LogLevel.WARNING, "Request failed. Req: " + request + "\nSpec: " + connection.getAddress() + + ", error code: " + request.errorCode()); + if (request.isError() && request.errorCode() == ErrorCode.CONNECTION || request.errorCode() == ErrorCode.TIMEOUT) { + log.log(LogLevel.WARNING, "Setting error for connection " + connection.getAddress()); + connectionPool.setError(connection, request.errorCode()); + } return false; } } - synchronized boolean isDownloading(FileReference fileReference) { - return downloads.containsKey(fileReference); + boolean isDownloading(FileReference fileReference) { + synchronized (downloads) { + return downloads.containsKey(fileReference); + } } - synchronized ListenableFuture<Optional<File>> addDownloadListener(FileReference fileReference, Runnable runnable) { - FileReferenceDownload fileReferenceDownload = downloads.get(fileReference); - fileReferenceDownload.future().addListener(runnable, downloadExecutor); - return fileReferenceDownload.future(); + ListenableFuture<Optional<File>> addDownloadListener(FileReference fileReference, Runnable runnable) { + synchronized (downloads) { + FileReferenceDownload download = downloads.get(fileReference); + if (download != null) { + download.future().addListener(runnable, downloadExecutor); + return download.future(); + } + } + return null; } private void execute(Request request, Connection connection) { @@ -170,15 +159,33 @@ class FileReferenceDownloader { } double downloadStatus(String file) { - return downloadStatus.getOrDefault(new FileReference(file), 0.0); + double status = 0.0; + synchronized (downloads) { + Double download = downloadStatus.get(new FileReference(file)); + if (download != null) { + status = download; + } + } + return status; + } + + void setDownloadStatus(String file, double completeness) { + setDownloadStatus(new FileReference(file), completeness); } - void setDownloadStatus(String file, double percentageDownloaded) { - downloadStatus.put(new FileReference(file), percentageDownloaded); + void setDownloadStatus(FileReference fileReference, double completeness) { + synchronized (downloads) { + downloadStatus.put(fileReference, completeness); + } } Map<FileReference, Double> downloadStatus() { - return ImmutableMap.copyOf(downloadStatus); + synchronized (downloads) { + return ImmutableMap.copyOf(downloadStatus); + } } + public ConnectionPool connectionPool() { + return connectionPool; + } } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java new file mode 100644 index 00000000000..1681843a818 --- /dev/null +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/LazyFileReferenceData.java @@ -0,0 +1,52 @@ +package com.yahoo.vespa.filedistribution; + +import com.yahoo.config.FileReference; +import net.jpountz.xxhash.StreamingXXHash64; +import net.jpountz.xxhash.XXHashFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; + +public class LazyFileReferenceData extends FileReferenceData { + private final File file; + private final ReadableByteChannel channel; + private final StreamingXXHash64 hasher; + public LazyFileReferenceData(FileReference fileReference, String filename, Type type, File file) throws IOException { + super(fileReference, filename, type); + this.file = file; + channel = Files.newByteChannel(file.toPath()); + this.hasher = XXHashFactory.fastestInstance().newStreamingHash64(0); + } + + @Override + public int nextContent(ByteBuffer bb) { + int read = 0; + int pos = bb.position(); + try { + read = channel.read(bb); + } catch (IOException e) { + return -1; + } + if (read > 0) { + hasher.update(bb.array(), pos, read); + } + return read; + } + + @Override + public long xxhash() { + return hasher.getValue(); + } + + @Override + public long size() { + try { + return Files.size(file.toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java new file mode 100644 index 00000000000..28935c203fe --- /dev/null +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/RpcTester.java @@ -0,0 +1,98 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.filedistribution; + +import com.yahoo.config.FileReference; +import com.yahoo.io.IOUtils; +import com.yahoo.jrt.DataValue; +import com.yahoo.jrt.Int32Value; +import com.yahoo.jrt.Int64Value; +import com.yahoo.jrt.Request; +import com.yahoo.jrt.Spec; +import com.yahoo.jrt.StringValue; +import com.yahoo.jrt.Supervisor; +import com.yahoo.jrt.Target; +import com.yahoo.jrt.Transport; +import com.yahoo.log.LogLevel; +import net.jpountz.xxhash.XXHash64; +import net.jpountz.xxhash.XXHashFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.logging.Logger; + +public class RpcTester { + + private static final Logger log = Logger.getLogger(RpcTester.class.getName()); + + private final Target target; + + private RpcTester(Target target) { + this.target = target; + } + + private void call(String fileReference, String filename, byte[] blob) { + new FileReceiver(target).receive(new FileReference(fileReference), filename, blob); + } + + public static void main(String[] args) { + //String fileReference = args[0]; + String fileReference = "59f93f445438c9db7ccbf1629f583c2aa004a68b"; + String filename = "com.yahoo.vespatest.ExtraHitSearcher-1.0.0-deploy.jar"; + File file = new File(String.format("/tmp/%s/%s", fileReference, filename)); + byte[] blob = null; + + try { + blob = IOUtils.readFileBytes(file); + } catch (IOException e) { + e.printStackTrace(); + } + + log.log(LogLevel.INFO, "Read blob from " + file.getAbsolutePath()); + + + Supervisor supervisor = new Supervisor(new Transport()); + + Spec spec = new Spec("tcp/localhost:19090"); + log.log(LogLevel.INFO, "Connecting to " + spec); + Target target = supervisor.connect(spec); + if (! target.isValid()) { + log.log(LogLevel.INFO, "Could not connect"); + System.exit(1); + } else { + log.log(LogLevel.INFO, "Connected to " + spec); + } + + new RpcTester(target).call(fileReference, filename, blob); + } + + class FileReceiver { + + Target target; + + FileReceiver(Target target) { + this.target = target; + } + + void receive(FileReference reference, String filename, byte[] content) { + + log.log(LogLevel.INFO, "Preparing receive call for " + reference.value() + " and file " + filename); + + XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); + Request fileBlob = new Request("filedistribution.receiveFile"); + + log.log(LogLevel.INFO, "Calling " + fileBlob.methodName() + " with target " + target); + + fileBlob.parameters().add(new StringValue(reference.value())); + fileBlob.parameters().add(new StringValue(filename)); + fileBlob.parameters().add(new DataValue(content)); + fileBlob.parameters().add(new Int64Value(hasher.hash(ByteBuffer.wrap(content), 0))); + fileBlob.parameters().add(new Int32Value(0)); + fileBlob.parameters().add(new StringValue("OK")); + log.log(LogLevel.INFO, "Doing invokeSync"); + target.invokeSync(fileBlob, 5); + log.log(LogLevel.INFO, "Done with invokeSync"); + } + } +} diff --git a/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java index 278c46dab8b..d2da020539a 100644 --- a/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java +++ b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileDownloaderTest.java @@ -13,15 +13,13 @@ import com.yahoo.jrt.Transport; import com.yahoo.text.Utf8; import com.yahoo.vespa.config.Connection; import com.yahoo.vespa.config.ConnectionPool; -import net.jpountz.xxhash.XXHash64; -import net.jpountz.xxhash.XXHashFactory; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.file.Files; +import java.nio.file.Path; import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -35,18 +33,18 @@ import static org.junit.Assert.fail; public class FileDownloaderTest { - private final XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); - private MockConnection connection; private FileDownloader fileDownloader; private File downloadDir; + private File tempDir; @Before public void setup() { try { downloadDir = Files.createTempDirectory("filedistribution").toFile(); + tempDir = Files.createTempDirectory("download").toFile(); connection = new MockConnection(); - fileDownloader = new FileDownloader(connection, downloadDir, Duration.ofMillis(2000)); + fileDownloader = new FileDownloader(connection, downloadDir, tempDir, Duration.ofMillis(2000)); } catch (IOException e) { e.printStackTrace(); fail(e.getMessage()); @@ -102,7 +100,7 @@ public class FileDownloaderTest { // Receives fileReference, should return and make it available to caller String filename = "abc.jar"; - receiveFile(fileReference, filename, "some other content"); + receiveFile(fileReference, filename, FileReferenceData.Type.file, "some other content"); Optional<File> downloadedFile = fileDownloader.getFile(fileReference); assertTrue(downloadedFile.isPresent()); @@ -113,11 +111,45 @@ public class FileDownloaderTest { // Verify download status when downloaded assertDownloadStatus(fileDownloader, fileReference, 100.0); } + + { + // fileReference does not exist on disk, needs to be downloaded, is compressed data + + FileReference fileReference = new FileReference("fileReferenceToDirWithManyFiles"); + File fileReferenceFullPath = fileReferenceFullPath(downloadDir, fileReference); + assertFalse(fileReferenceFullPath.getAbsolutePath(), fileDownloader.getFile(fileReference).isPresent()); + + // Verify download status + assertDownloadStatus(fileDownloader, fileReference, 0.0); + + // Receives fileReference, should return and make it available to caller + String filename = "abc.tar.gz"; + Path tempPath = Files.createTempDirectory("dir"); + File subdir = new File(tempPath.toFile(), "subdir"); + File fooFile = new File(subdir, "foo"); + IOUtils.writeFile(fooFile, "foo", false); + File barFile = new File(subdir, "bar"); + IOUtils.writeFile(barFile, "bar", false); + + File tarFile = CompressedFileReference.compress(tempPath.toFile(), Arrays.asList(fooFile, barFile), new File(tempPath.toFile(), filename)); + byte[] tarredContent = IOUtils.readFileBytes(tarFile); + receiveFile(fileReference, filename, FileReferenceData.Type.compressed, tarredContent); + Optional<File> downloadedFile = fileDownloader.getFile(fileReference); + + assertTrue(downloadedFile.isPresent()); + File downloadedFoo = new File(fileReferenceFullPath, tempPath.relativize(fooFile.toPath()).toString()); + File downloadedBar = new File(fileReferenceFullPath, tempPath.relativize(barFile.toPath()).toString()); + assertEquals("foo", IOUtils.readFile(downloadedFoo)); + assertEquals("bar", IOUtils.readFile(downloadedBar)); + + // Verify download status when downloaded + assertDownloadStatus(fileDownloader, fileReference, 100.0); + } } @Test public void getFileWhenConnectionError() throws IOException { - fileDownloader = new FileDownloader(connection, downloadDir, Duration.ofMillis(3000)); + fileDownloader = new FileDownloader(connection, downloadDir, tempDir, Duration.ofMillis(3000)); File downloadDir = fileDownloader.downloadDirectory(); int timesToFail = 2; @@ -133,7 +165,7 @@ public class FileDownloaderTest { // Receives fileReference, should return and make it available to caller String filename = "abc.jar"; - receiveFile(fileReference, filename, "some other content"); + receiveFile(fileReference, filename, FileReferenceData.Type.file, "some other content"); Optional<File> downloadedFile = fileDownloader.getFile(fileReference); assertTrue(downloadedFile.isPresent()); @@ -153,11 +185,11 @@ public class FileDownloaderTest { File downloadDir = Files.createTempDirectory("filedistribution").toFile(); MockConnection connectionPool = new MockConnection(); connectionPool.setResponseHandler(new MockConnection.WaitResponseHandler(timeout.plus(Duration.ofMillis(1000)))); - FileDownloader fileDownloader = new FileDownloader(connectionPool, downloadDir, timeout); + FileDownloader fileDownloader = new FileDownloader(connectionPool, downloadDir, tempDir, timeout); FileReference foo = new FileReference("foo"); FileReference bar = new FileReference("bar"); List<FileReference> fileReferences = Arrays.asList(foo, bar); - fileDownloader.queueForDownload(fileReferences); + fileDownloader.queueForAsyncDownload(fileReferences); // Verify download status assertDownloadStatus(fileDownloader, foo, 0.0); @@ -168,7 +200,7 @@ public class FileDownloaderTest { public void receiveFile() throws IOException { FileReference foo = new FileReference("foo"); String filename = "foo.jar"; - receiveFile(foo, filename, "content"); + receiveFile(foo, filename, FileReferenceData.Type.file, "content"); File downloadedFile = new File(fileReferenceFullPath(downloadDir, foo), filename); assertEquals("content", IOUtils.readFile(downloadedFile)); } @@ -187,10 +219,12 @@ public class FileDownloaderTest { assertEquals(expectedDownloadStatus, downloadStatus, 0.0001); } - private void receiveFile(FileReference fileReference, String filename, String content) { - byte[] contentBytes = Utf8.toBytes(content); - long xxHashFromContent = hasher.hash(ByteBuffer.wrap(contentBytes), 0); - fileDownloader.receiveFile(fileReference, filename, contentBytes, xxHashFromContent); + private void receiveFile(FileReference fileReference, String filename, FileReferenceData.Type type, String content) { + receiveFile(fileReference, filename, type, Utf8.toBytes(content)); + } + + private void receiveFile(FileReference fileReference, String filename, FileReferenceData.Type type, byte[] content) { + fileDownloader.receiveFile(new FileReferenceDataBlob(fileReference, filename, type, content)); } private static class MockConnection implements ConnectionPool, com.yahoo.vespa.config.Connection { diff --git a/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java new file mode 100644 index 00000000000..762817c27ef --- /dev/null +++ b/filedistribution/src/test/java/com/yahoo/vespa/filedistribution/FileReceiverTest.java @@ -0,0 +1,71 @@ +package com.yahoo.vespa.filedistribution; + +import com.yahoo.config.FileReference; +import com.yahoo.text.Utf8; +import net.jpountz.xxhash.XXHash64; +import net.jpountz.xxhash.XXHashFactory; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.assertEquals; + + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; + +public class FileReceiverTest { + private File root; + private File tempDir; + private final XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void setup() throws IOException { + root = temporaryFolder.newFolder("root"); + tempDir = temporaryFolder.newFolder("tmp"); + } + + @Test + public void receiveMultiPartFile() throws IOException{ + String [] parts = new String[3]; + parts[0] = "first part\n"; + parts[1] = "second part\n"; + parts[2] = "third part\n"; + StringBuilder sb = new StringBuilder(); + for (String s : parts) { + sb.append(s); + } + String all = sb.toString(); + transferPartsAndAssert(new FileReference("ref-a"), "myfile-1", all, 1); + transferPartsAndAssert(new FileReference("ref-a"), "myfile-2", all, 2); + transferPartsAndAssert(new FileReference("ref-a"), "myfile-3", all, 3); + } + + private void transferPartsAndAssert(FileReference ref, String fileName, String all, int numParts) throws IOException { + byte [] allContent = Utf8.toBytes(all); + + FileReceiver.Session session = new FileReceiver.Session(root, tempDir, 1, ref, + FileReferenceData.Type.file, fileName, allContent.length); + int partSize = (allContent.length+(numParts-1))/numParts; + ByteBuffer bb = ByteBuffer.wrap(allContent); + for (int i = 0, pos = 0; i < numParts; i++) { + byte [] buf = new byte[Math.min(partSize, allContent.length - pos)]; + bb.get(buf); + session.addPart(i, buf); + // Small numbers, so need a large delta + assertEquals((double)(i+1)/(double)numParts, session.percentageReceived(), 0.04); + pos += buf.length; + } + File file = session.close(hasher.hash(ByteBuffer.wrap(Utf8.toBytes(all)), 0)); + + byte [] allReadBytes = Files.readAllBytes(file.toPath()); + file.delete(); + assertEquals(all, Utf8.toString(allReadBytes)); + } +} diff --git a/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp b/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp index e07f0684584..de410289ec0 100644 --- a/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp +++ b/filedistribution/src/vespa/filedistribution/model/zkfacade.cpp @@ -4,7 +4,7 @@ #include <vespa/vespalib/net/socket_address.h> #include <vespa/filedistribution/common/logfwd.h> #include <vespa/defaults.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/vespalib/text/stringtokenizer.h> #include <zookeeper/zookeeper.h> #include <sstream> diff --git a/filedistribution_test/pom.xml b/filedistribution_test/pom.xml index c270eabb6cf..5cc113c6453 100644 --- a/filedistribution_test/pom.xml +++ b/filedistribution_test/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>filedistribution_test</artifactId> <version>6-SNAPSHOT</version> diff --git a/filedistributionmanager/pom.xml b/filedistributionmanager/pom.xml index 9e3ecf95ecb..9f037814a12 100644 --- a/filedistributionmanager/pom.xml +++ b/filedistributionmanager/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>filedistributionmanager</artifactId> <version>6-SNAPSHOT</version> diff --git a/fsa/pom.xml b/fsa/pom.xml index a49845105cf..2a56069f82e 100644 --- a/fsa/pom.xml +++ b/fsa/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>fsa</artifactId> <packaging>container-plugin</packaging> diff --git a/indexinglanguage/pom.xml b/indexinglanguage/pom.xml index 70cb9f6d8d0..f0aba8d9476 100644 --- a/indexinglanguage/pom.xml +++ b/indexinglanguage/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>indexinglanguage</artifactId> <packaging>jar</packaging> diff --git a/jaxrs_client_utils/pom.xml b/jaxrs_client_utils/pom.xml index 1c4bdde21a8..e3de5b8163d 100644 --- a/jaxrs_client_utils/pom.xml +++ b/jaxrs_client_utils/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jaxrs_client_utils</artifactId> <version>6-SNAPSHOT</version> diff --git a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java index 5fd72432702..0b3c7ad8d3b 100644 --- a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java +++ b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/JerseyJaxRsClientFactory.java @@ -6,10 +6,13 @@ import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.client.proxy.WebResourceFactory; -import javax.ws.rs.client.Client; +import javax.net.ssl.SSLContext; import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriBuilder; +import java.util.Collections; /** * @author bakksjo @@ -20,14 +23,26 @@ public class JerseyJaxRsClientFactory implements JaxRsClientFactory { private final int connectTimeoutMs; private final int readTimeoutMs; + private final SSLContext sslContext; + private final String userAgent; public JerseyJaxRsClientFactory() { this(DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } + public JerseyJaxRsClientFactory(SSLContext sslContext, String userAgent) { + this(DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS, sslContext, userAgent); + } + public JerseyJaxRsClientFactory(final int connectTimeoutMs, final int readTimeoutMs) { + this(connectTimeoutMs, readTimeoutMs, null, null); + } + + public JerseyJaxRsClientFactory(int connectTimeoutMs, int readTimeoutMs, SSLContext sslContext, String userAgent) { this.connectTimeoutMs = connectTimeoutMs; this.readTimeoutMs = readTimeoutMs; + this.sslContext = sslContext; + this.userAgent = userAgent; } /** @@ -38,13 +53,21 @@ public class JerseyJaxRsClientFactory implements JaxRsClientFactory { @Override public <T> T createClient(final Class<T> apiClass, final HostName hostName, final int port, final String pathPrefix, String scheme) { final UriBuilder uriBuilder = UriBuilder.fromPath(pathPrefix).host(hostName.s()).port(port).scheme(scheme); - final Client webClient = ClientBuilder.newClient() + ClientBuilder builder = ClientBuilder.newBuilder() .property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs) .property(ClientProperties.READ_TIMEOUT, readTimeoutMs) .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true) // Allow empty PUT. TODO: Fix API. .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) // Allow e.g. PATCH method. .property(ClientProperties.FOLLOW_REDIRECTS, true); - final WebTarget target = webClient.target(uriBuilder); + if (sslContext != null) { + builder.sslContext(sslContext); + builder.hostnameVerifier((s, sslSession) -> true); + } + if (userAgent != null) { + builder.register((ClientRequestFilter) context -> + context.getHeaders().put(HttpHeaders.USER_AGENT, Collections.singletonList(userAgent))); + } + final WebTarget target = builder.build().target(uriBuilder); // TODO: Check if this fills up non-heap memory with loaded classes. return WebResourceFactory.newResource(apiClass, target); } diff --git a/jaxrs_utils/pom.xml b/jaxrs_utils/pom.xml index 249cbda992b..1753d016ec3 100644 --- a/jaxrs_utils/pom.xml +++ b/jaxrs_utils/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jaxrs_utils</artifactId> <version>6-SNAPSHOT</version> diff --git a/jdisc_core/pom.xml b/jdisc_core/pom.xml index fb8200eb610..55da222994f 100644 --- a/jdisc_core/pom.xml +++ b/jdisc_core/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jdisc_core</artifactId> <version>6-SNAPSHOT</version> diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java index cd52a724275..a78e4f1af40 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/StandaloneMain.java @@ -47,7 +47,7 @@ public class StandaloneMain { loader.destroy(); System.out.println("debug\tStopped ok."); System.exit(0); - } catch (Exception e) { + } catch (Throwable e) { System.out.print("debug\tUnexpected: "); e.printStackTrace(); log.log(Level.SEVERE, "Unexpected: ", e); diff --git a/jdisc_core_test/pom.xml b/jdisc_core_test/pom.xml index 92e7a39fee0..a1a96463ffc 100644 --- a/jdisc_core_test/pom.xml +++ b/jdisc_core_test/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa.jdisc_core</groupId> <artifactId>parent</artifactId> diff --git a/jdisc_http_service/pom.xml b/jdisc_http_service/pom.xml index d8c0b0bc29c..a1482669b39 100644 --- a/jdisc_http_service/pom.xml +++ b/jdisc_http_service/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jdisc_http_service</artifactId> <version>6-SNAPSHOT</version> diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java index c3c83474e56..063bb35b63e 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java @@ -19,6 +19,7 @@ import java.net.URISyntaxException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -115,6 +116,10 @@ public class AccessLogRequestLog extends AbstractLifeCycle implements RequestLog if (principal != null) { accessLogEntry.setUserPrincipal(principal); } + X509Certificate[] clientCert = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + if (clientCert != null && clientCert.length > 0) { + accessLogEntry.setSslPrincipal(clientCert[0].getSubjectX500Principal()); + } } private static String getRemoteAddress(final HttpServletRequest request) { diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java index 8255e16e0ee..981d4219158 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java @@ -2,12 +2,18 @@ package com.yahoo.jdisc.http.server.jetty; import com.google.inject.Inject; +import com.yahoo.config.InnerNode; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ConnectorConfig.Ssl; -import com.yahoo.jdisc.http.SecretStore; +import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ExcludeCipherSuite; +import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ExcludeProtocol; +import com.yahoo.jdisc.http.ConnectorConfig.Ssl.IncludeCipherSuite; +import com.yahoo.jdisc.http.ConnectorConfig.Ssl.IncludeProtocol; import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreContext; +import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreContext; import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator; +import com.yahoo.jdisc.http.ssl.SslTrustStoreConfigurator; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -18,6 +24,9 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.util.ssl.SslContextFactory; import java.nio.channels.ServerSocketChannel; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; /** * @author Einar M R Rosenvinge @@ -26,27 +35,16 @@ import java.nio.channels.ServerSocketChannel; public class ConnectorFactory { private final ConnectorConfig connectorConfig; - private final SecretStore secretStore; private final SslKeyStoreConfigurator sslKeyStoreConfigurator; + private final SslTrustStoreConfigurator sslTrustStoreConfigurator; @Inject public ConnectorFactory(ConnectorConfig connectorConfig, - SecretStore secretStore, - SslKeyStoreConfigurator sslKeyStoreConfigurator) { + SslKeyStoreConfigurator sslKeyStoreConfigurator, + SslTrustStoreConfigurator sslTrustStoreConfigurator) { this.connectorConfig = connectorConfig; - this.secretStore = secretStore; this.sslKeyStoreConfigurator = sslKeyStoreConfigurator; - - if (connectorConfig.ssl().enabled()) - validateSslConfig(connectorConfig); - } - - // TODO: can be removed when we have dedicated SSL config in services.xml - private static void validateSslConfig(ConnectorConfig config) { - ConnectorConfig.Ssl ssl = config.ssl(); - if (!ssl.trustStorePath().isEmpty() && ssl.useTrustStorePassword() && ssl.keyDbKey().isEmpty()) { - throw new IllegalArgumentException("Missing password for JKS truststore"); - } + this.sslTrustStoreConfigurator = sslTrustStoreConfigurator; } public ConnectorConfig getConnectorConfig() { @@ -93,13 +91,13 @@ public class ConnectorFactory { return new HttpConnectionFactory(httpConfig); } - //TODO: does not support loading non-yahoo readable JKS key stores. private SslConnectionFactory newSslConnectionFactory() { Ssl sslConfig = connectorConfig.ssl(); SslContextFactory factory = new SslContextFactory(); sslKeyStoreConfigurator.configure(new DefaultSslKeyStoreContext(factory)); + sslTrustStoreConfigurator.configure(new DefaultSslTrustStoreContext(factory)); switch (sslConfig.clientAuth()) { case NEED_AUTH: @@ -114,49 +112,28 @@ public class ConnectorFactory { factory.setSecureRandomAlgorithm(sslConfig.prng()); } - if (!sslConfig.excludeProtocol().isEmpty()) { - String[] prots = new String[sslConfig.excludeProtocol().size()]; - for (int i = 0; i < prots.length; i++) { - prots[i] = sslConfig.excludeProtocol(i).name(); - } - factory.setExcludeProtocols(prots); - } - if (!sslConfig.includeProtocol().isEmpty()) { - String[] prots = new String[sslConfig.includeProtocol().size()]; - for (int i = 0; i < prots.length; i++) { - prots[i] = sslConfig.includeProtocol(i).name(); - } - factory.setIncludeProtocols(prots); - } - if (!sslConfig.excludeCipherSuite().isEmpty()) { - String[] ciphs = new String[sslConfig.excludeCipherSuite().size()]; - for (int i = 0; i < ciphs.length; i++) { - ciphs[i] = sslConfig.excludeCipherSuite(i).name(); - } - factory.setExcludeCipherSuites(ciphs); - - } - if (!sslConfig.includeCipherSuite().isEmpty()) { - String[] ciphs = new String[sslConfig.includeCipherSuite().size()]; - for (int i = 0; i < ciphs.length; i++) { - ciphs[i] = sslConfig.includeCipherSuite(i).name(); - } - factory.setIncludeCipherSuites(ciphs); - } - - String keyDbPassword = sslConfig.keyDbKey(); - - if (!sslConfig.trustStorePath().isEmpty()) { - factory.setTrustStorePath(sslConfig.trustStorePath()); - factory.setTrustStoreType(sslConfig.trustStoreType().toString()); - if (sslConfig.useTrustStorePassword()) { - factory.setTrustStorePassword(secretStore.getSecret(keyDbPassword)); - } - } + setStringArrayParameter( + factory, sslConfig.excludeProtocol(), ExcludeProtocol::name, SslContextFactory::setExcludeProtocols); + setStringArrayParameter( + factory, sslConfig.includeProtocol(), IncludeProtocol::name, SslContextFactory::setIncludeProtocols); + setStringArrayParameter( + factory, sslConfig.excludeCipherSuite(), ExcludeCipherSuite::name, SslContextFactory::setExcludeCipherSuites); + setStringArrayParameter( + factory, sslConfig.includeCipherSuite(), IncludeCipherSuite::name, SslContextFactory::setIncludeCipherSuites); factory.setKeyManagerFactoryAlgorithm(sslConfig.sslKeyManagerFactoryAlgorithm()); factory.setProtocol(sslConfig.protocol()); return new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString()); } + private static <T extends InnerNode> void setStringArrayParameter(SslContextFactory sslContextFactory, + List<T> configValues, + Function<T, String> nameProperty, + BiConsumer<SslContextFactory, String[]> setter) { + if (!configValues.isEmpty()) { + String[] nameArray = configValues.stream().map(nameProperty).toArray(String[]::new); + setter.accept(sslContextFactory, nameArray); + } + } + } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java index 714d75f9d1e..a005ea7d96e 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java @@ -3,6 +3,7 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.servlet.ServletRequest; import com.yahoo.jdisc.service.CurrentContainer; import javax.servlet.http.HttpServletRequest; @@ -27,7 +28,7 @@ class HttpRequestFactory { HttpRequest.Version.fromString(servletRequest.getProtocol()), new InetSocketAddress(servletRequest.getRemoteAddr(), servletRequest.getRemotePort()), getConnection(servletRequest).getCreatedTimeStamp()); - httpRequest.context().put("jdisc.request.X509Certificate", getCertChain(servletRequest)); + httpRequest.context().put(ServletRequest.JDISC_REQUEST_X509CERT, getCertChain(servletRequest)); return httpRequest; } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java index 735bf3ed89a..bc14a063cd1 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java @@ -184,8 +184,7 @@ public class ServletResponseController { private static void setHeaders_holdingLock(Response jdiscResponse, HttpServletResponse servletResponse) { for (final Map.Entry<String, String> entry : jdiscResponse.headers().entries()) { - final String value = entry.getValue(); - servletResponse.addHeader(entry.getKey(), value != null ? value : ""); + servletResponse.addHeader(entry.getKey(), entry.getValue()); } if (servletResponse.getContentType() == null) { diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java index db8780b087c..ea36237bc45 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java @@ -38,6 +38,7 @@ import static com.yahoo.jdisc.http.core.HttpServletRequestUtils.getConnection; */ public class ServletRequest extends HttpServletRequestWrapper implements ServletOrJdiscHttpRequest { public static final String JDISC_REQUEST_PRINCIPAL = "jdisc.request.principal"; + public static final String JDISC_REQUEST_X509CERT = "jdisc.request.X509Certificate"; private final HttpServletRequest request; private final HeaderFields headerFields; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java new file mode 100644 index 00000000000..8af21d48e9a --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreConfigurator.java @@ -0,0 +1,40 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +import com.google.inject.Inject; +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.SecretStore; + +/** + * @author bjorncs + */ +public class DefaultSslTrustStoreConfigurator implements SslTrustStoreConfigurator { + + private final SecretStore secretStore; + private final ConnectorConfig.Ssl config; + + @Inject + public DefaultSslTrustStoreConfigurator(ConnectorConfig config, SecretStore secretStore) { + validateConfig(config.ssl()); + this.secretStore = secretStore; + this.config = config.ssl(); + } + + @Override + public void configure(SslTrustStoreContext context) { + if (!config.enabled()) return; + String keyDbPassword = config.keyDbKey(); + if (!config.trustStorePath().isEmpty()) { + String password = config.useTrustStorePassword() ? secretStore.getSecret(keyDbPassword) : null; + context.updateTrustStore(config.trustStorePath(), config.trustStoreType().toString(), password); + } + } + + private static void validateConfig(ConnectorConfig.Ssl config) { + if (!config.enabled()) return; + if (!config.trustStorePath().isEmpty() && config.useTrustStorePassword() && config.keyDbKey().isEmpty()) { + throw new IllegalArgumentException("Missing password for JKS truststore"); + } + } + +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java new file mode 100644 index 00000000000..c2d91cca3ea --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslTrustStoreContext.java @@ -0,0 +1,54 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.security.KeyStore; +import java.util.function.Consumer; + +/** + * @author bjorncs + */ +public class DefaultSslTrustStoreContext implements SslTrustStoreContext { + + private final SslContextFactory sslContextFactory; + + public DefaultSslTrustStoreContext(SslContextFactory sslContextFactory) { + this.sslContextFactory = sslContextFactory; + } + + @Override + public void updateTrustStore(KeyStore trustStore) { + updateTrustStore(trustStore, null); + } + + @Override + public void updateTrustStore(KeyStore trustStore, String password) { + updateTrustStore(sslContextFactory -> { + sslContextFactory.setTrustStore(trustStore); + if (password != null) { + sslContextFactory.setTrustStorePassword(password); + } + }); + } + + @Override + public void updateTrustStore(String trustStorePath, String trustStoreType, String trustStorePassword) { + updateTrustStore(sslContextFactory -> { + sslContextFactory.setTrustStorePath(trustStorePath); + sslContextFactory.setTrustStoreType(trustStoreType); + if (trustStorePassword != null) { + sslContextFactory.setTrustStorePassword(trustStorePassword); + } + }); + } + + private void updateTrustStore(Consumer<SslContextFactory> reloader) { + try { + sslContextFactory.reload(reloader); + } catch (Exception e) { + throw new RuntimeException("Could not update truststore: " + e.getMessage(), e); + } + } + +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java new file mode 100644 index 00000000000..de1119a5275 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreConfigurator.java @@ -0,0 +1,14 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +/** + * An interface for an component that can configure an {@link SslTrustStoreContext}. The implementor can assume that + * the {@link SslTrustStoreContext} instance is thread-safe and be updated at any time + * during and after the call to{@link #configure(SslTrustStoreContext)}. + * Modifying the {@link SslKeyStoreContext} instance will trigger a hot reload of the truststore in JDisc. + * + * @author bjorncs + */ +public interface SslTrustStoreConfigurator { + void configure(SslTrustStoreContext context); +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java new file mode 100644 index 00000000000..fc8cf397b24 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslTrustStoreContext.java @@ -0,0 +1,16 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +import java.security.KeyStore; + +/** + * An interface to update the truststore in JDisc. Any update will trigger a hot reload and new connections will + * authenticated using the update truststore. + * + * @author bjorncs + */ +public interface SslTrustStoreContext { + void updateTrustStore(KeyStore trustStore); + void updateTrustStore(KeyStore trustStore, String password); + void updateTrustStore(String trustStorePath, String trustStoreType, String trustStorePassword); +} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java index 0d8f433cc39..6281907e083 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java @@ -12,6 +12,7 @@ import com.yahoo.jdisc.http.SecretStore; import com.yahoo.jdisc.http.server.jetty.ConnectorFactory; import com.yahoo.jdisc.http.server.jetty.TestDrivers; import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator; +import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator; /** * Guice module for test ConnectorFactories @@ -48,8 +49,8 @@ public class ConnectorFactoryRegistryModule implements Module { public StaticKeyDbConnectorFactory(ConnectorConfig connectorConfig) { super(connectorConfig, - new MockSecretStore(), - new DefaultSslKeyStoreConfigurator(connectorConfig, new MockSecretStore())); + new DefaultSslKeyStoreConfigurator(connectorConfig, new MockSecretStore()), + new DefaultSslTrustStoreConfigurator(connectorConfig, new MockSecretStore())); } } diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java index 781bc6a7b5f..103a317094b 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java @@ -5,6 +5,7 @@ import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.SecretStore; import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator; +import com.yahoo.jdisc.http.ssl.DefaultSslTrustStoreConfigurator; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -107,7 +108,9 @@ public class ConnectorFactoryTest { private static ConnectorFactory createConnectorFactory(ConnectorConfig config) { ThrowingSecretStore secretStore = new ThrowingSecretStore(); - return new ConnectorFactory(config, secretStore, new DefaultSslKeyStoreConfigurator(config, secretStore)); + return new ConnectorFactory(config, + new DefaultSslKeyStoreConfigurator(config, secretStore), + new DefaultSslTrustStoreConfigurator(config, secretStore)); } private static class HelloWorldHandler extends AbstractHandler { diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java index 7ed13decbf6..55dec64e967 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java @@ -371,6 +371,7 @@ public class HttpServerTest { } // Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2 + // Details in https://github.com/eclipse/jetty.project/issues/1116 @Test public void requireThatHeaderWithNullValueIsOmitted() throws Exception { final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", null)); @@ -380,13 +381,14 @@ public class HttpServerTest { assertThat(driver.close(), is(true)); } - // Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2 + // Header with empty value is allowed by https://tools.ietf.org/html/rfc7230#section-3.2 + // Details in https://github.com/eclipse/jetty.project/issues/1116 @Test - public void requireThatHeaderWithEmptyValueIsOmitted() throws Exception { + public void requireThatHeaderWithEmptyValueIsAllowed() throws Exception { final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", "")); driver.client().get("/status.html") .expectStatusCode(is(OK)) - .expectNoHeader("X-Foo"); + .expectHeader("X-Foo", is("")); assertThat(driver.close(), is(true)); } diff --git a/jdisc_jetty/pom.xml b/jdisc_jetty/pom.xml index 4e739aae8cc..0f8a5ba19e2 100644 --- a/jdisc_jetty/pom.xml +++ b/jdisc_jetty/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jdisc_jetty</artifactId> <version>6-SNAPSHOT</version> diff --git a/jdisc_messagebus_service/pom.xml b/jdisc_messagebus_service/pom.xml index e9531a73861..6201f413b9a 100644 --- a/jdisc_messagebus_service/pom.xml +++ b/jdisc_messagebus_service/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jdisc_messagebus_service</artifactId> <version>6-SNAPSHOT</version> diff --git a/jrt/pom.xml b/jrt/pom.xml index 0d6be56fdab..cf3da2ab7ce 100644 --- a/jrt/pom.xml +++ b/jrt/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>jrt</artifactId> <packaging>container-plugin</packaging> diff --git a/linguistics/pom.xml b/linguistics/pom.xml index 328742f483a..e4aa7c3049e 100644 --- a/linguistics/pom.xml +++ b/linguistics/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>linguistics</artifactId> <packaging>container-plugin</packaging> @@ -59,6 +60,7 @@ <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <scope>provided</scope> + <classifier>no_aop</classifier> </dependency> </dependencies> <build> diff --git a/logd/pom.xml b/logd/pom.xml index e21931efc5a..a1a107bc20c 100644 --- a/logd/pom.xml +++ b/logd/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>logd</artifactId> <version>6-SNAPSHOT</version> diff --git a/logd/src/apps/logd/main.cpp b/logd/src/apps/logd/main.cpp index 70df928dfd2..f9d760c4d18 100644 --- a/logd/src/apps/logd/main.cpp +++ b/logd/src/apps/logd/main.cpp @@ -6,6 +6,7 @@ #include <logd/conf.h> #include <logd/watch.h> #include <logd/state.h> +#include <logd/metrics.h> #include <vespa/config/common/exceptions.h> #include <csignal> #include <unistd.h> @@ -21,7 +22,9 @@ using config::FileSpec; int main(int, char**) { - Forwarder fwd; + StateReporter stateReporter; + Metrics metrics(stateReporter.metrics()); + Forwarder fwd(metrics); EV_STARTED("logdemon"); @@ -31,7 +34,6 @@ int main(int, char**) try { ConfSub subscriber(fwd, config::ConfigUri(cfid)); - StateReporter stateReporter; int sleepcount = 0; while (true) { diff --git a/logd/src/logd/CMakeLists.txt b/logd/src/logd/CMakeLists.txt index 3ef48f9255a..78892fdeca6 100644 --- a/logd/src/logd/CMakeLists.txt +++ b/logd/src/logd/CMakeLists.txt @@ -1,14 +1,16 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. vespa_add_library(logd STATIC SOURCES - watch.cpp - conn.cpp + cmdbuf.cpp conf.cpp + conn.cpp forward.cpp - service.cpp - cmdbuf.cpp + metrics.cpp perform.cpp + service.cpp state.cpp + watch.cpp + DEPENDS ) vespa_generate_config(logd ../main/resources/configdefinitions/logd.def) diff --git a/logd/src/logd/conf.cpp b/logd/src/logd/conf.cpp index 716ba97effd..ef1f6b73cbe 100644 --- a/logd/src/logd/conf.cpp +++ b/logd/src/logd/conf.cpp @@ -35,12 +35,12 @@ ConfSub::configure(std::unique_ptr<LogdConfig> cfg) return; } strcpy(_logServer, newconf.logserver.host.c_str()); - _newConf = true; + _needToConnect = true; } if (newconf.logserver.use != _use_logserver) { _use_logserver = newconf.logserver.use; - _newConf = true; + _needToConnect = true; } _statePort = newconf.stateport; @@ -57,7 +57,7 @@ ConfSub::configure(std::unique_ptr<LogdConfig> cfg) if (newconf.logserver.port != _logPort) { _logPort = newconf.logserver.port; - _newConf = true; + _needToConnect = true; } if (newconf.rotate.size > 0) { _rotate_size = newconf.rotate.size; @@ -85,12 +85,23 @@ ConfSub::configure(std::unique_ptr<LogdConfig> cfg) } } +bool +ConfSub::checkAvailable() +{ + if (_subscriber.nextGeneration(0)) { + _hasAvailable = true; + } + return _hasAvailable; +} + void ConfSub::latch() { - if (_subscriber.nextConfig(0)) + if (checkAvailable()) { configure(_handle->getConfig()); - if (_newConf) { + _hasAvailable = false; + } + if (_needToConnect) { if (_use_logserver) { connectToLogserver(); } else { @@ -133,7 +144,7 @@ ConfSub::resetFileDescriptor(int newfd) } _logserverfd = newfd; _fw.setLogserverFD(newfd); - _newConf = false; + _needToConnect = false; } void @@ -141,7 +152,7 @@ ConfSub::closeConn() { close(_logserverfd); _logserverfd = -1; - _newConf = true; + _needToConnect = true; } ConfSub::ConfSub(Forwarder &fw, const config::ConfigUri & configUri) @@ -156,7 +167,8 @@ ConfSub::ConfSub(Forwarder &fw, const config::ConfigUri & configUri) _fw(fw), _subscriber(configUri.getContext()), _handle(), - _newConf(false) + _hasAvailable(false), + _needToConnect(true) { _logServer[0] = '\0'; _handle = _subscriber.subscribe<LogdConfig>(configUri.getConfigId()); diff --git a/logd/src/logd/conf.h b/logd/src/logd/conf.h index 7a91630ff93..a777470efc6 100644 --- a/logd/src/logd/conf.h +++ b/logd/src/logd/conf.h @@ -19,7 +19,8 @@ private: Forwarder& _fw; config::ConfigSubscriber _subscriber; config::ConfigHandle<cloud::config::log::LogdConfig>::UP _handle; - bool _newConf; + bool _hasAvailable; + bool _needToConnect; void connectToLogserver(); void connectToDevNull(); @@ -27,6 +28,7 @@ private: ConfSub(const ConfSub& other); ConfSub& operator=(const ConfSub& other); public: + bool checkAvailable(); void latch(); void closeConn(); ConfSub(Forwarder &fw, const config::ConfigUri & configUri); diff --git a/logd/src/logd/forward.cpp b/logd/src/logd/forward.cpp index 66031ba2539..eba38dbcbd3 100644 --- a/logd/src/logd/forward.cpp +++ b/logd/src/logd/forward.cpp @@ -11,8 +11,9 @@ LOG_SETUP(""); namespace logdemon { -Forwarder::Forwarder() +Forwarder::Forwarder(Metrics &metrics) : _logserverfd(-1), + _metrics(metrics), _forwardMap(), _levelparser(), knownServices(), @@ -172,6 +173,8 @@ Forwarder::parseline(const char *linestart, const char *lineend) return false; } + _metrics.countLine(level, service); + // Check overrides ForwardMap::iterator found = _forwardMap.find(l); if (found != _forwardMap.end()) { diff --git a/logd/src/logd/forward.h b/logd/src/logd/forward.h index 794db3bb45f..e012db205fe 100644 --- a/logd/src/logd/forward.h +++ b/logd/src/logd/forward.h @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once #include "service.h" +#include "metrics.h" #include <vespa/vespalib/util/hashmap.h> #include <map> @@ -23,6 +24,7 @@ class Forwarder { private: int _logserverfd; + Metrics &_metrics; ForwardMap _forwardMap; LevelParser _levelparser; const char *copystr(const char *b, const char *e) { @@ -36,7 +38,7 @@ private: public: Services knownServices; int _badLines; - Forwarder(); + Forwarder(Metrics &metrics); ~Forwarder(); void forwardText(const char *text, int len); void forwardLine(const char *line, const char *eol); diff --git a/logd/src/logd/metrics.cpp b/logd/src/logd/metrics.cpp new file mode 100644 index 00000000000..1a70a2226e2 --- /dev/null +++ b/logd/src/logd/metrics.cpp @@ -0,0 +1,3 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "metrics.h" diff --git a/logd/src/logd/metrics.h b/logd/src/logd/metrics.h new file mode 100644 index 00000000000..b5bc5a7dcd7 --- /dev/null +++ b/logd/src/logd/metrics.h @@ -0,0 +1,39 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/metrics/metrics_manager.h> + +namespace logdemon { + +using vespalib::metrics::Dimension; +using vespalib::metrics::Counter; +using vespalib::metrics::MetricsManager; +using vespalib::metrics::Point; + +struct Metrics { + std::shared_ptr<MetricsManager> metrics; + const Dimension loglevel; + const Dimension servicename; + const Counter loglines; + + Metrics(std::shared_ptr<MetricsManager> m) + : metrics(m), + loglevel(metrics->dimension("loglevel")), + servicename(metrics->dimension("service")), + loglines(metrics->counter("logd.processed.lines", + "how many log lines have been processed")) + {} + + ~Metrics() {} + + void countLine(const vespalib::string &level, + const vespalib::string &service) const + { + Point p = metrics->pointBuilder() + .bind(loglevel, level) + .bind(servicename, service); + loglines.add(1, p); + } +}; + +} // namespace logdemon diff --git a/logd/src/logd/state.cpp b/logd/src/logd/state.cpp index b73183d5987..e1df1412215 100644 --- a/logd/src/logd/state.cpp +++ b/logd/src/logd/state.cpp @@ -4,10 +4,20 @@ LOG_SETUP(""); #include "state.h" +#include <vespa/vespalib/metrics/simple_metrics_manager.h> namespace logdemon { +using vespalib::metrics::SimpleMetricsManager; +using vespalib::metrics::SimpleManagerConfig; + StateReporter::StateReporter() + : _port(-1), + _server(), + _health(), + _components(), + _metrics(SimpleMetricsManager::create(SimpleManagerConfig())), + _producer(_metrics) { } @@ -16,7 +26,7 @@ StateReporter::setStatePort(int statePort) { if (statePort != _port) { _port = statePort; - _server.reset(new vespalib::StateServer(_port, _health, _metrics, _components)); + _server.reset(new vespalib::StateServer(_port, _health, _producer, _components)); LOG(info, "state server listening on port %d", _server->getListenPort()); } } diff --git a/logd/src/logd/state.h b/logd/src/logd/state.h index 7abcc7eb93c..59c9741e68f 100644 --- a/logd/src/logd/state.h +++ b/logd/src/logd/state.h @@ -3,9 +3,10 @@ #include <vespa/vespalib/net/state_server.h> #include <vespa/vespalib/net/simple_health_producer.h> -#include <vespa/vespalib/net/simple_metrics_producer.h> #include <vespa/vespalib/net/simple_component_config_producer.h> #include <vespa/vespalib/net/generic_state_handler.h> +#include <vespa/vespalib/metrics/metrics_manager.h> +#include <vespa/vespalib/metrics/producer.h> namespace logdemon { @@ -13,12 +14,15 @@ class StateReporter { int _port; std::unique_ptr<vespalib::StateServer> _server; vespalib::SimpleHealthProducer _health; - vespalib::SimpleMetricsProducer _metrics; vespalib::SimpleComponentConfigProducer _components; + std::shared_ptr<vespalib::metrics::MetricsManager> _metrics; + vespalib::metrics::Producer _producer; public: StateReporter(); + ~StateReporter() {} void setStatePort(int statePort); void gotConf(size_t generation); + std::shared_ptr<vespalib::metrics::MetricsManager> metrics() { return _metrics; } }; } // namespace diff --git a/logd/src/logd/watch.cpp b/logd/src/logd/watch.cpp index ad866938ab6..2adc1cbbdbb 100644 --- a/logd/src/logd/watch.cpp +++ b/logd/src/logd/watch.cpp @@ -332,7 +332,10 @@ Watcher::watchfile() dcf.saveState(already, _forwarder.knownServices); - _confsubscriber.latch(); + if (_confsubscriber.checkAvailable()) { + LOG(debug, "new config available, doing reconfigure"); + return; + } if (_confsubscriber.useLogserver()) { cmdbuf.maybeRead(_forwarder.getLogserverFD()); diff --git a/logd/src/tests/forward/forward.cpp b/logd/src/tests/forward/forward.cpp index 13e31cea464..db0a4e5de69 100644 --- a/logd/src/tests/forward/forward.cpp +++ b/logd/src/tests/forward/forward.cpp @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include <vespa/log/log.h> #include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/metrics/dummy_metrics_manager.h> #include <logd/forward.h> #include <sstream> #include <fcntl.h> @@ -49,14 +50,17 @@ struct ForwardFixture { } }; -TEST_FF("require that forwarder forwards if set", Forwarder(), ForwardFixture(f1, "forward.txt")) { +std::shared_ptr<vespalib::metrics::MetricsManager> dummy = vespalib::metrics::DummyMetricsManager::create(); +Metrics m(dummy); + +TEST_FF("require that forwarder forwards if set", Forwarder(m), ForwardFixture(f1, "forward.txt")) { ForwardMap forwardMap; forwardMap[Logger::event] = true; f1.setForwardMap(forwardMap); f2.verifyForward(true); } -TEST_FF("require that forwarder does not forward if not set", Forwarder(), ForwardFixture(f1, "forward.txt")) { +TEST_FF("require that forwarder does not forward if not set", Forwarder(m), ForwardFixture(f1, "forward.txt")) { ForwardMap forwardMap; forwardMap[Logger::event] = false; f1.setForwardMap(forwardMap); diff --git a/logserver/pom.xml b/logserver/pom.xml index 6acdd8ed1d7..55c5c443cfb 100644 --- a/logserver/pom.xml +++ b/logserver/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>logserver</artifactId> <packaging>jar</packaging> diff --git a/maven-plugins/pom.xml b/maven-plugins/pom.xml index 2f7343e753c..0aabe630a63 100644 --- a/maven-plugins/pom.xml +++ b/maven-plugins/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa</groupId> <artifactId>bundle-plugins</artifactId> diff --git a/memfilepersistence/src/tests/spi/memcachetest.cpp b/memfilepersistence/src/tests/spi/memcachetest.cpp index 73815df563a..5e9f1a28225 100644 --- a/memfilepersistence/src/tests/spi/memcachetest.cpp +++ b/memfilepersistence/src/tests/spi/memcachetest.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 <vespa/memfilepersistence/memfile/memfilecache.h> -#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h> #include <tests/spi/memfiletestutils.h> @@ -41,7 +40,6 @@ private: framework::defaultimplementation::ComponentRegisterImpl::UP _register; framework::Component::UP _component; FakeClock::UP _clock; - framework::defaultimplementation::MemoryManager::UP _memoryManager; std::unique_ptr<MemFilePersistenceMetrics> _metrics; std::unique_ptr<MemFileCache> _cache; @@ -113,18 +111,10 @@ private: new framework::defaultimplementation::ComponentRegisterImpl); _clock.reset(new FakeClock); _register->setClock(*_clock); - _memoryManager.reset( - new framework::defaultimplementation::MemoryManager( - framework::defaultimplementation::AllocationLogic::UP( - new framework::defaultimplementation::SimpleMemoryLogic( - *_clock, maxMemory * 2)))); - _register->setMemoryManager(*_memoryManager); _component.reset(new framework::Component(*_register, "testcomponent")); _metrics.reset(new MemFilePersistenceMetrics(*_component)); _cache.reset(new MemFileCache(*_register, _metrics->_cache)); setCacheSize(maxMemory); - _memoryManager->registerAllocationType(framework::MemoryAllocationType( - "steal", framework::MemoryAllocationType::FORCE_ALLOCATE)); } public: @@ -133,7 +123,6 @@ public: _metrics.reset(0); _component.reset(0); _register.reset(0); - _memoryManager.reset(0); _clock.reset(0); } }; diff --git a/memfilepersistence/src/tests/spi/memfiletestutils.cpp b/memfilepersistence/src/tests/spi/memfiletestutils.cpp index bb35aa4d03e..cd14e989220 100644 --- a/memfilepersistence/src/tests/spi/memfiletestutils.cpp +++ b/memfilepersistence/src/tests/spi/memfiletestutils.cpp @@ -5,7 +5,6 @@ #include <tests/spi/memfiletestutils.h> #include <tests/spi/simulatedfailurefile.h> #include <vespa/memfilepersistence/memfile/memfilecache.h> -#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h> #include <vespa/document/update/assignvalueupdate.h> #include <vespa/document/test/make_bucket_space.h> #include <vespa/persistence/spi/test.h> @@ -72,12 +71,6 @@ MemFileTestUtils::setupDisks(uint32_t numDisks) { new framework::defaultimplementation::ComponentRegisterImpl); _clock.reset(new FakeClock); _componentRegister->setClock(*_clock); - _memoryManager.reset( - new framework::defaultimplementation::MemoryManager( - framework::defaultimplementation::AllocationLogic::UP( - new framework::defaultimplementation::SimpleMemoryLogic( - *_clock, 1024 * 1024 * 1024)))); - _componentRegister->setMemoryManager(*_memoryManager); _env.reset(new MemFileTestEnvironment(numDisks, *_componentRegister, *getTypeRepo())); diff --git a/memfilepersistence/src/tests/spi/memfiletestutils.h b/memfilepersistence/src/tests/spi/memfiletestutils.h index dde73c88164..450d87451b9 100644 --- a/memfilepersistence/src/tests/spi/memfiletestutils.h +++ b/memfilepersistence/src/tests/spi/memfiletestutils.h @@ -20,8 +20,6 @@ #include <vespa/document/update/documentupdate.h> #include <vespa/storageframework/defaultimplementation/clock/realclock.h> #include <vespa/storageframework/defaultimplementation/component/componentregisterimpl.h> -#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h> - namespace storage { namespace memfile { @@ -70,7 +68,6 @@ private: document::BucketIdFactory _bucketIdFactory; framework::defaultimplementation::ComponentRegisterImpl::UP _componentRegister; FakeClock::UP _clock; - framework::defaultimplementation::MemoryManager::UP _memoryManager; std::unique_ptr<MemFileTestEnvironment> _env; public: @@ -82,7 +79,6 @@ public: void tearDown() override{ _env.reset(); _componentRegister.reset(); - _memoryManager.reset(); _clock.reset(); } diff --git a/memfilepersistence/src/tests/spi/providerconformancetest.cpp b/memfilepersistence/src/tests/spi/providerconformancetest.cpp index db02e619782..625549f0008 100644 --- a/memfilepersistence/src/tests/spi/providerconformancetest.cpp +++ b/memfilepersistence/src/tests/spi/providerconformancetest.cpp @@ -2,7 +2,6 @@ #include "memfiletestutils.h" #include <vespa/persistence/conformancetest/conformancetest.h> -#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h> namespace storage { namespace memfile { @@ -11,19 +10,13 @@ struct ProviderConformanceTest : public spi::ConformanceTest { struct Factory : public PersistenceFactory { framework::defaultimplementation::ComponentRegisterImpl _compRegister; framework::defaultimplementation::RealClock _clock; - framework::defaultimplementation::MemoryManager _memoryManager; std::unique_ptr<MemFileCache> cache; Factory() : _compRegister(), - _clock(), - _memoryManager( - framework::defaultimplementation::AllocationLogic::UP( - new framework::defaultimplementation::SimpleMemoryLogic( - _clock, 1024 * 1024 * 1024))) + _clock() { _compRegister.setClock(_clock); - _compRegister.setMemoryManager(_memoryManager); } spi::PersistenceProvider::UP diff --git a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp index 5f24d28a309..cfd73d1a7cb 100644 --- a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp +++ b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.cpp @@ -31,7 +31,7 @@ MemFileCache::returnToCache(MemFileCache::Entry& entry) MemoryUsage newUsage = entry._file.getCacheSize(); - if (_memoryToken->getSize() == 0 || newUsage.sum() == 0) { + if (_cacheLimit.sum() == 0 || newUsage.sum() == 0) { entry._file.flushToDisk(); eraseNoLock(id); return; @@ -155,10 +155,6 @@ MemFileCache::MemFileCache(framework::ComponentRegister& componentRegister, MemFilePersistenceCacheMetrics& metrics) : Component(componentRegister, "memfilecache"), _lastUsedCounter(0), - _allocationType(getMemoryManager().registerAllocationType( - framework::MemoryAllocationType( - "memfilecache", framework::MemoryAllocationType::CACHE))), - _memoryToken(getMemoryManager().allocate(_allocationType, 0, 0, 200)), _metrics(metrics), _bodyEvicter(_metrics.body_evictions), _headerEvicter(_metrics.header_evictions), @@ -172,7 +168,6 @@ MemFileCache::setCacheSize(MemoryUsage cacheSize) vespalib::LockGuard lock(_cacheLock); _cacheLimit = cacheSize; - _memoryToken->resize(std::min(_memoryToken->getSize(), _cacheLimit.sum()), _cacheLimit.sum()); evictWhileFull(); } @@ -491,7 +486,7 @@ MemFileCache::Statistics MemFileCache::getCacheStats() const { vespalib::LockGuard lock(_cacheLock); - return Statistics(_memoryUsage, _memoryToken->getSize(), _entries.size()); + return Statistics(_memoryUsage, _cacheLimit.sum(), _entries.size()); } void diff --git a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h index e45f8f7f1f6..36e6e6b641a 100644 --- a/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h +++ b/memfilepersistence/src/vespa/memfilepersistence/memfile/memfilecache.h @@ -21,7 +21,6 @@ #include <boost/multi_index/mem_fun.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/sequenced_index.hpp> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageframework/generic/component/component.h> @@ -191,8 +190,6 @@ private: LRUCache _entries; uint64_t _lastUsedCounter; - const framework::MemoryAllocationType& _allocationType; - framework::MemoryToken::UP _memoryToken; MemFilePersistenceCacheMetrics& _metrics; @@ -273,7 +270,6 @@ public: /** * Used for unit testing only. */ - framework::MemoryToken& getMemoryToken() { return *_memoryToken; } const MemFilePersistenceCacheMetrics& getMetrics() const { return _metrics; } @@ -283,7 +279,7 @@ public: */ void setCacheSize(MemoryUsage limits); - uint64_t getCacheSize() { return _memoryToken->getSize(); } + uint64_t getCacheSize() { return _cacheLimit.sum(); } /** * NOTE: takes lock, never call from within memfilecache code. diff --git a/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp b/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp index 1dccd255ebb..9781c28823d 100644 --- a/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp +++ b/memfilepersistence/src/vespa/memfilepersistence/tools/dumpslotfile.cpp @@ -11,7 +11,6 @@ #include <vespa/memfilepersistence/spi/memfilepersistenceprovidermetrics.h> #include <vespa/storageframework/defaultimplementation/clock/realclock.h> #include <vespa/storageframework/defaultimplementation/component/componentregisterimpl.h> -#include <vespa/storageframework/defaultimplementation/memory/nomemorymanager.h> #include <vespa/vespalib/util/programoptions.h> #include <vespa/config/helper/configgetter.hpp> #include <vespa/config/subscription/configuri.h> @@ -139,7 +138,6 @@ namespace { framework::defaultimplementation::ComponentRegisterImpl _compReg; framework::Component _component; framework::defaultimplementation::RealClock _clock; - framework::defaultimplementation::NoMemoryManager _memoryMan; MemFilePersistenceMetrics _metrics; MemFilePersistenceThreadMetrics* _threadMetrics; std::unique_ptr<MemFileCache> _cache; @@ -177,7 +175,6 @@ namespace { _docType("foo", 1) { _compReg.setClock(_clock); - _compReg.setMemoryManager(_memoryMan); _cache.reset(new MemFileCache(_compReg, _metrics._cache)); if (documentConfigId == 0) { _repo.reset(new DocumentTypeRepo(_docType)); diff --git a/messagebus-disc/pom.xml b/messagebus-disc/pom.xml index 46158b0ca60..89741dce82b 100644 --- a/messagebus-disc/pom.xml +++ b/messagebus-disc/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>messagebus-disc</artifactId> <version>6-SNAPSHOT</version> diff --git a/messagebus/pom.xml b/messagebus/pom.xml index 7fb44787cdf..7c37bccbd9a 100644 --- a/messagebus/pom.xml +++ b/messagebus/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>messagebus</artifactId> <version>6-SNAPSHOT</version> diff --git a/messagebus/src/vespa/messagebus/messagebus.cpp b/messagebus/src/vespa/messagebus/messagebus.cpp index fa646b55221..fd1ad2908c7 100644 --- a/messagebus/src/vespa/messagebus/messagebus.cpp +++ b/messagebus/src/vespa/messagebus/messagebus.cpp @@ -8,6 +8,7 @@ #include "protocolrepository.h" #include <vespa/messagebus/network/inetwork.h> #include <vespa/vespalib/util/exceptions.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/log/log.h> LOG_SETUP(".messagebus"); diff --git a/messagebus/src/vespa/messagebus/messenger.cpp b/messagebus/src/vespa/messagebus/messenger.cpp index 4b612b66c31..63df7f2f482 100644 --- a/messagebus/src/vespa/messagebus/messenger.cpp +++ b/messagebus/src/vespa/messagebus/messenger.cpp @@ -2,6 +2,7 @@ #include "messenger.h" #include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/log/log.h> LOG_SETUP(".messenger"); diff --git a/metrics/pom.xml b/metrics/pom.xml index ec64d0d392c..628ab66552d 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <groupId>com.yahoo.vespa</groupId> <artifactId>metrics</artifactId> diff --git a/node-admin/pom.xml b/node-admin/pom.xml index f7691cda3f7..983e4d3a832 100644 --- a/node-admin/pom.xml +++ b/node-admin/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>node-admin</artifactId> diff --git a/node-admin/src/main/application/services.xml b/node-admin/src/main/application/services.xml new file mode 100644 index 00000000000..030c42ac8c5 --- /dev/null +++ b/node-admin/src/main/application/services.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<jdisc id="node-admin" jetty="true" version="1.0"> + <!-- Please update container test when changing this file --> + <accesslog type="vespa" fileNamePattern="logs/vespa/node-admin/access.log.%Y%m%d%H%M%S" rotationScheme="date" symlinkName="access.log" /> + <handler id="com.yahoo.vespa.hosted.node.admin.restapi.RestApiHandler" bundle="node-admin"> + <binding>http://*/rest/*</binding> + </handler> + <component id="node-admin" class="com.yahoo.vespa.hosted.node.admin.provider.NodeAdminProvider" bundle="node-admin"/> + <component id="docker-api" class="com.yahoo.vespa.hosted.dockerapi.DockerImpl" bundle="docker-api"/> + <component id="metrics-wrapper" class="com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper" bundle="docker-api"/> + + <config name='vespa.hosted.dockerapi.docker'> + <isRunningLocally>false</isRunningLocally> + </config> + + <config name='vespa.hosted.node.admin.node-admin'> + <isRunningLocally>false</isRunningLocally> + <restartOnDeploy>true</restartOnDeploy> + </config> + + <nodes type="host"/> +</jdisc> diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java index d3af55cdff5..d2863468ee7 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java @@ -18,11 +18,13 @@ import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger; import java.io.IOException; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import static com.yahoo.vespa.defaults.Defaults.getDefaults; @@ -99,7 +101,9 @@ public class DockerOperationsImpl implements DockerOperations { InetAddress nodeInetAddress = environment.getInetAddressForHost(nodeSpec.hostname); final boolean isIPv6 = nodeInetAddress instanceof Inet6Address; - String configServers = String.join(",", environment.getConfigServerHosts()); + String configServers = environment.getConfigServerUris().stream() + .map(URI::getHost) + .collect(Collectors.joining(",")); Docker.CreateContainerCommand command = docker.createContainerCommand( nodeSpec.wantedDockerImage.get(), ContainerResources.from(nodeSpec.minCpuCores, nodeSpec.minMainMemoryAvailableGb), diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java index a3a647e1d14..f41d7deb04b 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java @@ -21,6 +21,7 @@ import com.yahoo.vespa.hosted.node.admin.util.SecretAgentScheduleMaker; import java.io.IOException; import java.io.InputStreamReader; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -139,7 +140,7 @@ public class StorageMaintainer { Process duCommand = new ProcessBuilder().command(command).start(); if (!duCommand.waitFor(60, TimeUnit.SECONDS)) { duCommand.destroy(); - throw new RuntimeException("Disk usage command timedout, aborting."); + throw new RuntimeException("Disk usage command timed out, aborting."); } String output = IOUtils.readAll(new InputStreamReader(duCommand.getInputStream())); String[] results = output.split("\t"); @@ -310,8 +311,8 @@ public class StorageMaintainer { * @throws RuntimeException if exit code != 0 */ public String getHardwareDivergence() { - String configServers = environment.getConfigServerHosts().stream() - .map(configServer -> "http://" + configServer + ":" + 4080) + String configServers = environment.getConfigServerUris().stream() + .map(URI::getHost) .collect(Collectors.joining(",")); return executeMaintainer("com.yahoo.vespa.hosted.node.verification.spec.SpecVerifier", configServers); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java index 906e6b55640..e380e7a2e26 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java @@ -34,6 +34,7 @@ import java.util.stream.Collectors; import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.RESUMED; import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN; +import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.TRANSITIONING; /** * Pulls information from node repository and forwards containers to run to node admin. @@ -110,15 +111,15 @@ public class NodeAdminStateUpdater { return this.getClass().getSimpleName() + "@" + Integer.toString(System.identityHashCode(this)); } - public enum State { RESUMED, SUSPENDED_NODE_ADMIN, SUSPENDED} + public enum State { TRANSITIONING, RESUMED, SUSPENDED_NODE_ADMIN, SUSPENDED} public Map<String, Object> getDebugPage() { Map<String, Object> debug = new LinkedHashMap<>(); synchronized (monitor) { debug.put("dockerHostHostName", dockerHostHostName); + debug.put("wantedState", wantedState); + debug.put("currentState", currentState); debug.put("NodeAdmin", nodeAdmin.debugInfo()); - debug.put("Wanted State: ", wantedState); - debug.put("Current State: ", currentState); } return debug; } @@ -185,7 +186,7 @@ public class NodeAdminStateUpdater { log.log(LogLevel.ERROR, "Error while trying to converge to " + wantedStateCopy, e); } - if (wantedStateCopy != RESUMED && currentState == RESUMED) { + if (wantedStateCopy != RESUMED && currentState == TRANSITIONING) { Duration subsystemFreezeDuration = nodeAdmin.subsystemFreezeDuration(); if (subsystemFreezeDuration.compareTo(FREEZE_CONVERGENCE_TIMEOUT) > 0) { // We have spent too much time trying to freeze and node admin is still not frozen. @@ -203,8 +204,9 @@ public class NodeAdminStateUpdater { * with respect to: freeze, Orchestrator, and services running. */ private void convergeState(State wantedState) { - if (currentState == wantedState) { - return; + if (currentState == wantedState) return; + synchronized (monitor) { + currentState = TRANSITIONING; } boolean wantFrozen = wantedState != RESUMED; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java index 09eb14039e8..b8a8bbfd6ad 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java @@ -524,6 +524,7 @@ public class NodeAgentImpl implements NodeAgent { Docker.ContainerStats stats = containerStats.get(); final String APP = MetricReceiverWrapper.APPLICATION_NODE; final int totalNumCpuCores = ((List<Number>) ((Map) stats.getCpuStats().get("cpu_usage")).get("percpu_usage")).size(); + final long cpuContainerKernelTime = ((Number) ((Map) stats.getCpuStats().get("cpu_usage")).get("usage_in_kernelmode")).longValue(); final long cpuContainerTotalTime = ((Number) ((Map) stats.getCpuStats().get("cpu_usage")).get("total_usage")).longValue(); final long cpuSystemTotalTime = ((Number) stats.getCpuStats().get("system_cpu_usage")).longValue(); final long memoryTotalBytes = ((Number) stats.getMemoryStats().get("limit")).longValue(); @@ -532,26 +533,28 @@ public class NodeAgentImpl implements NodeAgent { final long diskTotalBytes = (long) (nodeSpec.minDiskAvailableGb * BYTES_IN_GB); final Optional<Long> diskTotalBytesUsed = storageMaintainer.getDiskUsageFor(containerName); - // CPU usage by a container as percentage of total host CPU, cpuPercentageOfHost, is given by dividing used - // CPU time by the container with CPU time used by the entire system. - // CPU usage by a container as percentage of total CPU allocated to it is given by dividing the - // cpuPercentageOfHost with the ratio of container minCpuCores by total number of CPU cores. - double cpuPercentageOfHost = lastCpuMetric.getCpuUsagePercentage(cpuContainerTotalTime, cpuSystemTotalTime); - double cpuPercentageOfAllocated = totalNumCpuCores * cpuPercentageOfHost / nodeSpec.minCpuCores; + lastCpuMetric.updateCpuDeltas(cpuSystemTotalTime, cpuContainerTotalTime, cpuContainerKernelTime); + + // Ratio of CPU cores allocated to this container to total number of CPU cores on this host + final double allocatedCpuRatio = nodeSpec.minCpuCores / totalNumCpuCores; + double cpuUsageRatioOfAllocated = lastCpuMetric.getCpuUsageRatio() / allocatedCpuRatio; + double cpuKernelUsageRatioOfAllocated = lastCpuMetric.getCpuKernelUsageRatio() / allocatedCpuRatio; + long memoryTotalBytesUsed = memoryTotalBytesUsage - memoryTotalBytesCache; - double memoryPercentUsed = 100.0 * memoryTotalBytesUsed / memoryTotalBytes; - Optional<Double> diskPercentUsed = diskTotalBytesUsed.map(used -> 100.0 * used / diskTotalBytes); + double memoryUsageRatio = (double) memoryTotalBytesUsed / memoryTotalBytes; + Optional<Double> diskUsageRatio = diskTotalBytesUsed.map(used -> (double) used / diskTotalBytes); List<DimensionMetrics> metrics = new ArrayList<>(); DimensionMetrics.Builder systemMetricsBuilder = new DimensionMetrics.Builder(APP, dimensions) .withMetric("mem.limit", memoryTotalBytes) .withMetric("mem.used", memoryTotalBytesUsed) - .withMetric("mem.util", memoryPercentUsed) - .withMetric("cpu.util", cpuPercentageOfAllocated) + .withMetric("mem.util", 100 * memoryUsageRatio) + .withMetric("cpu.util", 100 * cpuUsageRatioOfAllocated) + .withMetric("cpu.sys.util", 100 * cpuKernelUsageRatioOfAllocated) .withMetric("disk.limit", diskTotalBytes); diskTotalBytesUsed.ifPresent(diskUsed -> systemMetricsBuilder.withMetric("disk.used", diskUsed)); - diskPercentUsed.ifPresent(diskUtil -> systemMetricsBuilder.withMetric("disk.util", diskUtil)); + diskUsageRatio.ifPresent(diskRatio -> systemMetricsBuilder.withMetric("disk.util", 100 * diskRatio)); metrics.add(systemMetricsBuilder.build()); stats.getNetworks().forEach((interfaceName, interfaceStats) -> { @@ -612,17 +615,35 @@ public class NodeAgentImpl implements NodeAgent { } class CpuUsageReporter { + private long containerKernelUsage = 0; private long totalContainerUsage = 0; private long totalSystemUsage = 0; - double getCpuUsagePercentage(long currentContainerUsage, long currentSystemUsage) { - long deltaSystemUsage = currentSystemUsage - totalSystemUsage; - double cpuUsagePct = (deltaSystemUsage == 0 || totalSystemUsage == 0) ? - 0 : 100.0 * (currentContainerUsage - totalContainerUsage) / deltaSystemUsage; + private long deltaContainerKernelUsage; + private long deltaContainerUsage; + private long deltaSystemUsage; + + private void updateCpuDeltas(long totalSystemUsage, long totalContainerUsage, long containerKernelUsage) { + deltaSystemUsage = this.totalSystemUsage == 0 ? 0 : (totalSystemUsage - this.totalSystemUsage); + deltaContainerUsage = totalContainerUsage - this.totalContainerUsage; + deltaContainerKernelUsage = containerKernelUsage - this.containerKernelUsage; + + this.totalSystemUsage = totalSystemUsage; + this.totalContainerUsage = totalContainerUsage; + this.containerKernelUsage = containerKernelUsage; + } + + /** + * Returns the CPU usage ratio for the docker container that this NodeAgent is managing + * in the time between the last two times updateCpuDeltas() was called. This is calculated + * by dividing the CPU time used by the container with the CPU time used by the entire system. + */ + double getCpuUsageRatio() { + return deltaSystemUsage == 0 ? Double.NaN : (double) deltaContainerUsage / deltaSystemUsage; + } - totalContainerUsage = currentContainerUsage; - totalSystemUsage = currentSystemUsage; - return cpuUsagePct; + double getCpuKernelUsageRatio() { + return deltaSystemUsage == 0 ? Double.NaN : (double) deltaContainerKernelUsage / deltaSystemUsage; } } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java index 8283c90e43d..b5c439e209a 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java @@ -31,11 +31,9 @@ public class NodeRepositoryImpl implements NodeRepository { private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(NodeRepositoryImpl.class); private final ConfigServerHttpRequestExecutor requestExecutor; - private final int port; - public NodeRepositoryImpl(ConfigServerHttpRequestExecutor requestExecutor, int port) { + public NodeRepositoryImpl(ConfigServerHttpRequestExecutor requestExecutor) { this.requestExecutor = requestExecutor; - this.port = port; } @Override @@ -43,7 +41,6 @@ public class NodeRepositoryImpl implements NodeRepository { try { final GetNodesResponse nodesForHost = requestExecutor.get( "/nodes/v2/node/?parentHost=" + baseHostName + "&recursive=true", - port, GetNodesResponse.class); if (nodesForHost.nodes == null) { @@ -71,7 +68,6 @@ public class NodeRepositoryImpl implements NodeRepository { public Optional<ContainerNodeSpec> getContainerNodeSpec(String hostName) { try { GetNodesResponse.Node nodeResponse = requestExecutor.get("/nodes/v2/node/" + hostName, - port, GetNodesResponse.Node.class); if (nodeResponse == null) { return Optional.empty(); @@ -86,7 +82,7 @@ public class NodeRepositoryImpl implements NodeRepository { public List<ContainerAclSpec> getContainerAclSpecs(String hostName) { try { final String path = String.format("/nodes/v2/acl/%s?children=true", hostName); - final GetAclResponse response = requestExecutor.get(path, port, GetAclResponse.class); + final GetAclResponse response = requestExecutor.get(path, GetAclResponse.class); return response.trustedNodes.stream() .map(node -> new ContainerAclSpec( node.hostname, node.ipAddress, ContainerName.fromHostname(node.trustedBy))) @@ -96,7 +92,7 @@ public class NodeRepositoryImpl implements NodeRepository { } } - public static ContainerNodeSpec createContainerNodeSpec(GetNodesResponse.Node node) + private static ContainerNodeSpec createContainerNodeSpec(GetNodesResponse.Node node) throws IllegalArgumentException, NullPointerException { Objects.requireNonNull(node.nodeState, "Unknown node state"); Node.State nodeState = Node.State.valueOf(node.nodeState); @@ -145,7 +141,6 @@ public class NodeRepositoryImpl implements NodeRepository { public void updateNodeAttributes(final String hostName, final NodeAttributes nodeAttributes) { UpdateNodeAttributesResponse response = requestExecutor.patch( "/nodes/v2/node/" + hostName, - port, new UpdateNodeAttributesRequestBody(nodeAttributes), UpdateNodeAttributesResponse.class); @@ -170,7 +165,6 @@ public class NodeRepositoryImpl implements NodeRepository { private void markNodeToState(String hostName, String state) { NodeMessageResponse response = requestExecutor.put( "/nodes/v2/state/" + state + "/" + hostName, - port, Optional.empty(), /* body */ NodeMessageResponse.class); NODE_ADMIN_LOGGER.info(response.message); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java index d1e996f8e93..7093f3f12e7 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java @@ -26,11 +26,9 @@ public class OrchestratorImpl implements Orchestrator { = ORCHESTRATOR_PATH_PREFIX + HostSuspensionApi.PATH_PREFIX; private final ConfigServerHttpRequestExecutor requestExecutor; - private final int port; - public OrchestratorImpl(ConfigServerHttpRequestExecutor requestExecutor, int port) { + public OrchestratorImpl(ConfigServerHttpRequestExecutor requestExecutor) { this.requestExecutor = requestExecutor; - this.port = port; } @Override @@ -38,7 +36,6 @@ public class OrchestratorImpl implements Orchestrator { UpdateHostResponse response; try { response = requestExecutor.put(getSuspendPath(hostName), - port, Optional.empty(), /* body */ UpdateHostResponse.class); } catch (HttpException.NotFoundException n) { @@ -61,7 +58,6 @@ public class OrchestratorImpl implements Orchestrator { try { batchOperationResult = requestExecutor.put( ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - port, Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), BatchOperationResult.class); } catch (HttpException e) { @@ -81,7 +77,7 @@ public class OrchestratorImpl implements Orchestrator { UpdateHostResponse response; try { String path = getSuspendPath(hostName); - response = requestExecutor.delete(path, port, UpdateHostResponse.class); + response = requestExecutor.delete(path, UpdateHostResponse.class); } catch (HttpException.NotFoundException n) { throw new OrchestratorNotFoundException("Failed to resume " + hostName + ", host not found"); } catch (HttpException e) { diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java index 109dbab924c..a343e431b5a 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java @@ -29,8 +29,6 @@ import java.time.Clock; import java.time.Duration; import java.util.function.Function; -import static com.yahoo.vespa.defaults.Defaults.getDefaults; - /** * Set up node admin for production. * @@ -38,7 +36,6 @@ import static com.yahoo.vespa.defaults.Defaults.getDefaults; */ public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> { - private static final int WEB_SERVICE_PORT = getDefaults().vespaWebServicePort(); private static final Duration NODE_AGENT_SCAN_INTERVAL = Duration.ofSeconds(30); private static final Duration NODE_ADMIN_CONVERGE_STATE_INTERVAL = Duration.ofSeconds(30); @@ -51,9 +48,9 @@ public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> { ProcessExecuter processExecuter = new ProcessExecuter(); Environment environment = new Environment(); - ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(environment.getConfigServerHosts()); - NodeRepository nodeRepository = new NodeRepositoryImpl(requestExecutor, WEB_SERVICE_PORT); - Orchestrator orchestrator = new OrchestratorImpl(requestExecutor, WEB_SERVICE_PORT); + ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(environment.getConfigServerUris()); + NodeRepository nodeRepository = new NodeRepositoryImpl(requestExecutor); + Orchestrator orchestrator = new OrchestratorImpl(requestExecutor); DockerOperations dockerOperations = new DockerOperationsImpl(docker, environment, processExecuter); StorageMaintainer storageMaintainer = new StorageMaintainer(docker, processExecuter, metricReceiver, environment, clock); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java index e825fa4ac8f..3576f37eb9a 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.node.admin.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpHeaders; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; @@ -12,18 +13,27 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.security.GeneralSecurityException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.Set; /** * Retries request on config server a few times before giving up. Assumes that all requests should be sent with @@ -33,11 +43,11 @@ import java.util.Set; */ public class ConfigServerHttpRequestExecutor { private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(ConfigServerHttpRequestExecutor.class); + private static final int MAX_LOOPS = 2; private final ObjectMapper mapper = new ObjectMapper(); private final CloseableHttpClient client; - private final List<String> configServerHosts; - private final static int MAX_LOOPS = 2; + private final List<URI> configServerHosts; @Override public void finalize() throws Throwable { @@ -50,31 +60,56 @@ public class ConfigServerHttpRequestExecutor { super.finalize(); } - public static ConfigServerHttpRequestExecutor create(Set<String> configServerHosts) { - if (configServerHosts.isEmpty()) { - throw new IllegalStateException("Environment setting for config servers missing or empty."); - } + public static ConfigServerHttpRequestExecutor create(Collection<URI> configServerUris) { + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(getConnectionSocketFactoryRegistry()); + cm.setMaxTotal(200); // Increase max total connections to 200, which should be enough + + // Have experienced hang in socket read, which may have been because of + // system defaults, therefore set explicit timeouts. Set arbitrarily to + // 15s > 10s used by Orchestrator lock timeout. + int timeoutMs = 15_000; + RequestConfig requestBuilder = RequestConfig.custom() + .setConnectTimeout(timeoutMs) // establishment of connection + .setConnectionRequestTimeout(timeoutMs) // connection from connection manager + .setSocketTimeout(timeoutMs) // waiting for data + .build(); + + return new ConfigServerHttpRequestExecutor(randomizeConfigServerUris(configServerUris), + HttpClientBuilder.create() + .setDefaultRequestConfig(requestBuilder) + .disableAutomaticRetries() + .setUserAgent("node-admin") + .setConnectionManager(cm).build()); + } - PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); - // Increase max total connections to 200, which should be enough - cm.setMaxTotal(200); - return new ConfigServerHttpRequestExecutor(configServerHosts, - HttpClientBuilder.create().disableAutomaticRetries().setConnectionManager(cm).build()); + private static Registry<ConnectionSocketFactory> getConnectionSocketFactoryRegistry() { + try { + SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( + new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build(), + NoopHostnameVerifier.INSTANCE); + + return RegistryBuilder.<ConnectionSocketFactory>create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Failed to create SSL context", e); + } } - ConfigServerHttpRequestExecutor(Set<String> configServerHosts, CloseableHttpClient client) { - this.configServerHosts = randomizeConfigServerHosts(configServerHosts); + ConfigServerHttpRequestExecutor(List<URI> configServerHosts, CloseableHttpClient client) { + this.configServerHosts = configServerHosts; this.client = client; } public interface CreateRequest { - HttpUriRequest createRequest(String configserver) throws JsonProcessingException, UnsupportedEncodingException; + HttpUriRequest createRequest(URI configServerUri) throws JsonProcessingException, UnsupportedEncodingException; } private <T> T tryAllConfigServers(CreateRequest requestFactory, Class<T> wantedReturnType) { Exception lastException = null; for (int loopRetry = 0; loopRetry < MAX_LOOPS; loopRetry++) { - for (String configServer : configServerHosts) { + for (URI configServer : configServerHosts) { final CloseableHttpResponse response; try { response = client.execute(requestFactory.createRequest(configServer)); @@ -118,9 +153,9 @@ public class ConfigServerHttpRequestExecutor { + configServerHosts + ") failed, last as follows:", lastException); } - public <T> T put(String path, int port, Optional<Object> bodyJsonPojo, Class<T> wantedReturnType) { + public <T> T put(String path, Optional<Object> bodyJsonPojo, Class<T> wantedReturnType) { return tryAllConfigServers(configServer -> { - HttpPut put = new HttpPut("http://" + configServer + ":" + port + path); + HttpPut put = new HttpPut(configServer.resolve(path)); setContentTypeToApplicationJson(put); if (bodyJsonPojo.isPresent()) { put.setEntity(new StringEntity(mapper.writeValueAsString(bodyJsonPojo.get()))); @@ -129,28 +164,28 @@ public class ConfigServerHttpRequestExecutor { }, wantedReturnType); } - public <T> T patch(String path, int port, Object bodyJsonPojo, Class<T> wantedReturnType) { + public <T> T patch(String path, Object bodyJsonPojo, Class<T> wantedReturnType) { return tryAllConfigServers(configServer -> { - HttpPatch patch = new HttpPatch("http://" + configServer + ":" + port + path); + HttpPatch patch = new HttpPatch(configServer.resolve(path)); setContentTypeToApplicationJson(patch); patch.setEntity(new StringEntity(mapper.writeValueAsString(bodyJsonPojo))); return patch; }, wantedReturnType); } - public <T> T delete(String path, int port, Class<T> wantedReturnType) { + public <T> T delete(String path, Class<T> wantedReturnType) { return tryAllConfigServers(configServer -> - new HttpDelete("http://" + configServer + ":" + port + path), wantedReturnType); + new HttpDelete(configServer.resolve(path)), wantedReturnType); } - public <T> T get(String path, int port, Class<T> wantedReturnType) { + public <T> T get(String path, Class<T> wantedReturnType) { return tryAllConfigServers(configServer -> - new HttpGet("http://" + configServer + ":" + port + path), wantedReturnType); + new HttpGet(configServer.resolve(path)), wantedReturnType); } - public <T> T post(String path, int port, Object bodyJsonPojo, Class<T> wantedReturnType) { + public <T> T post(String path, Object bodyJsonPojo, Class<T> wantedReturnType) { return tryAllConfigServers(configServer -> { - HttpPost post = new HttpPost("http://" + configServer + ":" + port + path); + HttpPost post = new HttpPost(configServer.resolve(path)); setContentTypeToApplicationJson(post); post.setEntity(new StringEntity(mapper.writeValueAsString(bodyJsonPojo))); return post; @@ -161,9 +196,9 @@ public class ConfigServerHttpRequestExecutor { request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); } - // Shuffle config server hosts to balance load - private List<String> randomizeConfigServerHosts(Set<String> configServerHosts) { - List<String> shuffledConfigServerHosts = new ArrayList<>(configServerHosts); + // Shuffle config server URIs to balance load + private static List<URI> randomizeConfigServerUris(Collection<URI> configServerUris) { + List<URI> shuffledConfigServerHosts = new ArrayList<>(configServerUris); Collections.shuffle(shuffledConfigServerHosts); return shuffledConfigServerHosts; } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java index 65a4d84b4c3..2b5e7990617 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/Environment.java @@ -6,6 +6,7 @@ import com.yahoo.net.HostName; import com.yahoo.vespa.hosted.dockerapi.ContainerName; import java.net.InetAddress; +import java.net.URI; import java.net.UnknownHostException; import java.nio.file.Path; import java.nio.file.Paths; @@ -15,9 +16,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.TimeZone; import java.util.stream.Collectors; @@ -31,13 +30,15 @@ public class Environment { private static final DateFormat filenameFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); public static final String APPLICATION_STORAGE_CLEANUP_PATH_PREFIX = "cleanup_"; - private static final String ENV_CONFIGSERVERS = "services__addr_configserver"; + private static final String ENV_CONFIGSERVER_SCHEME = "CONFIG_SERVER_SCHEME"; + private static final String ENV_CONFIGSERVER_HOSTS = "CONFIG_SERVER_ADDRESS"; + private static final String ENV_CONFIGSERVER_PORT = "CONFIG_SERVER_PORT"; private static final String ENVIRONMENT = "ENVIRONMENT"; private static final String REGION = "REGION"; private static final String LOGSTASH_NODES = "LOGSTASH_NODES"; private static final String COREDUMP_FEED_ENDPOINT = "COREDUMP_FEED_ENDPOINT"; - private final Set<String> configServerHosts; + private final List<URI> configServerHosts; private final String environment; private final String region; private final String parentHostHostname; @@ -51,7 +52,7 @@ public class Environment { } public Environment() { - this(getConfigServerHostsFromEnvironment(), + this(getConfigServerUrlsFromEnvironment(), getEnvironmentVariable(ENVIRONMENT), getEnvironmentVariable(REGION), HostName.getLocalhost(), @@ -61,7 +62,7 @@ public class Environment { getEnvironmentVariable(COREDUMP_FEED_ENDPOINT)); } - public Environment(Set<String> configServerHosts, + public Environment(List<URI> configServerHosts, String environment, String region, String parentHostHostname, @@ -79,7 +80,7 @@ public class Environment { this.feedEndpoint = feedEndpoint; } - public Set<String> getConfigServerHosts() { return configServerHosts; } + public List<URI> getConfigServerUris() { return configServerHosts; } public String getEnvironment() { return environment; @@ -95,7 +96,9 @@ public class Environment { private static String getEnvironmentVariable(String name) { final String value = System.getenv(name); - if (value == null) throw new IllegalStateException(String.format("Environment variable %s not set", name)); + if (Strings.isNullOrEmpty(value)) { + throw new IllegalStateException(String.format("Environment variable %s not set", name)); + } return value; } @@ -103,14 +106,14 @@ public class Environment { return getEnvironment() + "." + getRegion(); } - private static Set<String> getConfigServerHostsFromEnvironment() { - String configServerHosts = System.getenv(ENV_CONFIGSERVERS); - if (configServerHosts == null) { - return Collections.emptySet(); - } + private static List<URI> getConfigServerUrlsFromEnvironment() { + String scheme = getEnvironmentVariable(ENV_CONFIGSERVER_SCHEME); + String configServerHosts = getEnvironmentVariable(ENV_CONFIGSERVER_HOSTS); + String port = getEnvironmentVariable(ENV_CONFIGSERVER_PORT); - final List<String> hostNameStrings = Arrays.asList(configServerHosts.split("[,\\s]+")); - return new HashSet<>(hostNameStrings); + return Arrays.stream(configServerHosts.split("[,\\s]+")) + .map(hostname -> URI.create(scheme + "://" + hostname + ":" + port)) + .collect(Collectors.toList()); } private static List<String> getLogstashNodesFromEnvironment() { @@ -188,7 +191,7 @@ public class Environment { } public static class Builder { - private Set<String> configServerHosts = Collections.emptySet(); + private List<URI> configServerHosts = Collections.emptyList(); private String environment; private String region; private String parentHostHostname; @@ -197,8 +200,10 @@ public class Environment { private List<String> logstashNodes = Collections.emptyList(); private String feedEndpoint; - public Builder configServerHosts(String... hosts) { - configServerHosts = Arrays.stream(hosts).collect(Collectors.toSet()); + public Builder configServerUris(String... hosts) { + configServerHosts = Arrays.stream(hosts) + .map(URI::create) + .collect(Collectors.toList()); return this; } diff --git a/node-admin/src/main/sh/node-admin.sh b/node-admin/src/main/sh/node-admin.sh new file mode 100755 index 00000000000..ff0ea492318 --- /dev/null +++ b/node-admin/src/main/sh/node-admin.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# BEGIN environment bootstrap section +# Do not edit between here and END as this section should stay identical in all scripts + +findpath () { + myname=${0} + mypath=${myname%/*} + myname=${myname##*/} + if [ "$mypath" ] && [ -d "$mypath" ]; then + return + fi + mypath=$(pwd) + if [ -f "${mypath}/${myname}" ]; then + return + fi + echo "FATAL: Could not figure out the path where $myname lives from $0" + exit 1 +} + +COMMON_ENV=libexec/vespa/common-env.sh + +source_common_env () { + if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then + export VESPA_HOME + common_env=$VESPA_HOME/$COMMON_ENV + if [ -f "$common_env" ]; then + . $common_env + return + fi + fi + return 1 +} + +findroot () { + source_common_env && return + if [ "$VESPA_HOME" ]; then + echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" + exit 1 + fi + if [ "$ROOT" ] && [ -d "$ROOT" ]; then + VESPA_HOME="$ROOT" + source_common_env && return + fi + findpath + while [ "$mypath" ]; do + VESPA_HOME=${mypath} + source_common_env && return + mypath=${mypath%/*} + done + echo "FATAL: missing VESPA_HOME environment variable" + echo "Could not locate $COMMON_ENV anywhere" + exit 1 +} + +findroot + +# END environment bootstrap section + +Usage() { + cat <<EOF +Usage: ${0##*/} [start|stop] +Manage standalone node admin +EOF + + exit 1 +} + +if (( $# != 1 )); then + Usage +fi + +case "$1" in + start) + "$VESPA_HOME"/libexec/vespa/standalone-container.sh start -s node-admin -u root + ;; + stop) + "$VESPA_HOME"/libexec/vespa/standalone-container.sh stop -s node-admin -u root + ;; + *) Usage ;; +esac diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java index ce062702a3b..1058278a02c 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java @@ -13,8 +13,9 @@ import org.junit.Test; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -50,26 +51,13 @@ public class NodeAdminStateUpdaterTest { @Test public void testStateConvergence() throws IOException { - List<ContainerNodeSpec> containersToRun = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - containersToRun.add( - new ContainerNodeSpec.Builder() - .hostname("host" + i + ".test.yahoo.com") - .nodeState(i % 3 == 0 ? Node.State.active : Node.State.ready) - .nodeType("tenant") - .nodeFlavor("docker") - .minCpuCores(1) - .minMainMemoryAvailableGb(1) - .minDiskAvailableGb(1) - .build()); - } - List<String> activeHostnames = Arrays.asList( - "host0.test.yahoo.com", "host3.test.yahoo.com", "host6.test.yahoo.com", "host9.test.yahoo.com"); + mockNodeRepo(4); + List<String> activeHostnames = nodeRepository.getContainersToRun(parentHostname).stream() + .map(node -> node.hostname) + .collect(Collectors.toList()); List<String> suspendHostnames = new ArrayList<>(activeHostnames); suspendHostnames.add(parentHostname); - when(nodeRepository.getContainersToRun(eq(parentHostname))).thenReturn(containersToRun); - // Initially everything is frozen to force convergence assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED)); when(nodeAdmin.setFrozen(eq(false))).thenReturn(true); @@ -148,7 +136,7 @@ public class NodeAdminStateUpdaterTest { assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.SUSPENDED)); verify(refresher, times(2)).signalWorkToBeDone(); // No change in desired state verifyNoMoreInteractions(nodeAdmin); - + // Lets try going back to resumed when(nodeAdmin.setFrozen(eq(false))).thenReturn(false).thenReturn(true); // NodeAgents not converged to yet assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED)); @@ -163,6 +151,50 @@ public class NodeAdminStateUpdaterTest { assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED)); } + @Test + public void half_transition_revert() throws IOException { + mockNodeRepo(3); + + // Initially everything is frozen to force convergence + when(nodeAdmin.setFrozen(eq(false))).thenReturn(true); + doNothing().when(orchestrator).resume(parentHostname); + tickAfter(0); // The first tick should unfreeze + assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED)); + verify(nodeAdmin, times(1)).setFrozen(eq(false)); + + // Let's start suspending, we are able to freeze the nodes, but orchestrator denies suspension + when(nodeAdmin.subsystemFreezeDuration()).thenReturn(Duration.ofSeconds(1)); + when(nodeAdmin.setFrozen(eq(true))).thenReturn(true); + doThrow(new RuntimeException("Cannot allow to suspend because some reason")) + .when(orchestrator).suspend(eq(parentHostname)); + + assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN)); + tickAfter(0); + verify(nodeAdmin, times(1)).setFrozen(eq(true)); + + // We change our mind, want to remain resumed + assertFalse(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED)); + tickAfter(0); + assertTrue(refresher.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.RESUMED)); + verify(nodeAdmin, times(2)).setFrozen(eq(false)); // Make sure that we unfreeze! + } + + private void mockNodeRepo(int numberOfNodes) throws IOException { + List<ContainerNodeSpec> containersToRun = IntStream.range(0, numberOfNodes) + .mapToObj(i -> new ContainerNodeSpec.Builder() + .hostname("host" + i + ".test.yahoo.com") + .nodeState(Node.State.active) + .nodeType("tenant") + .nodeFlavor("docker") + .minCpuCores(1) + .minMainMemoryAvailableGb(1) + .minDiskAvailableGb(1) + .build()) + .collect(Collectors.toList()); + + when(nodeRepository.getContainersToRun(eq(parentHostname))).thenReturn(containersToRun); + } + private void tickAfter(int seconds) { clock.advance(Duration.ofSeconds(seconds)); refresher.tick(); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java index 7456b07d2f4..45f5de3d1a5 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import java.io.IOException; import java.net.ServerSocket; +import java.net.URI; import java.time.Instant; import java.util.Collections; import java.util.List; @@ -35,9 +36,7 @@ import static org.junit.Assert.fail; */ public class NodeRepositoryImplTest { private JDisc container; - private int port; - private final ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create( - Collections.singleton("127.0.0.1")); + private ConfigServerHttpRequestExecutor requestExecutor; private int findRandomOpenPort() throws IOException { @@ -56,14 +55,15 @@ public class NodeRepositoryImplTest { */ @Before public void startContainer() throws Exception { - port = findRandomOpenPort(); - System.err.println("PORT IS " + port); + final int port = findRandomOpenPort(); + requestExecutor = ConfigServerHttpRequestExecutor.create( + Collections.singleton(URI.create("http://127.0.0.1:" + port))); container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(port), Networking.enable); } private void waitForJdiscContainerToServe() throws InterruptedException { Instant start = Instant.now(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); while (Instant.now().minusSeconds(120).isBefore(start)) { try { nodeRepositoryApi.getContainersToRun("foobar"); @@ -85,7 +85,7 @@ public class NodeRepositoryImplTest { @Test public void testGetContainersToRunApi() throws IOException, InterruptedException { waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); String dockerHostHostname = "dockerhost1.yahoo.com"; final List<ContainerNodeSpec> containersToRun = nodeRepositoryApi.getContainersToRun(dockerHostHostname); @@ -104,7 +104,7 @@ public class NodeRepositoryImplTest { @Test public void testGetContainer() throws InterruptedException, IOException { waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); String hostname = "host4.yahoo.com"; Optional<ContainerNodeSpec> nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname); assertThat(nodeSpec.isPresent(), is(true)); @@ -114,7 +114,7 @@ public class NodeRepositoryImplTest { @Test public void testGetContainerForNonExistingNode() throws InterruptedException, IOException { waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); String hostname = "host-that-does-not-exist"; Optional<ContainerNodeSpec> nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname); assertFalse(nodeSpec.isPresent()); @@ -123,7 +123,7 @@ public class NodeRepositoryImplTest { @Test public void testUpdateNodeAttributes() throws InterruptedException, IOException { waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); String hostname = "host4.yahoo.com"; nodeRepositoryApi.updateNodeAttributes( hostname, @@ -136,7 +136,7 @@ public class NodeRepositoryImplTest { @Test(expected = RuntimeException.class) public void testUpdateNodeAttributesWithBadValue() throws InterruptedException, IOException { waitForJdiscContainerToServe(); - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); String hostname = "host4.yahoo.com"; nodeRepositoryApi.updateNodeAttributes( hostname, @@ -148,7 +148,7 @@ public class NodeRepositoryImplTest { @Test public void testMarkAsReady() throws InterruptedException, IOException { - NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor, port); + NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor); waitForJdiscContainerToServe(); nodeRepositoryApi.markAsDirty("host5.yahoo.com"); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java index 0a11ddd1e62..779a0a6a376 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java @@ -24,15 +24,12 @@ public class OrchestratorImplTest { private static final String hostName = "host123.yahoo.com"; private final ConfigServerHttpRequestExecutor requestExecutor = mock(ConfigServerHttpRequestExecutor.class); - private final int port = 1234; - private final OrchestratorImpl orchestrator = new OrchestratorImpl(requestExecutor, port); - + private final OrchestratorImpl orchestrator = new OrchestratorImpl(requestExecutor); @Test public void testSuspendCall() { when(requestExecutor.put( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - port, Optional.empty(), UpdateHostResponse.class )).thenReturn(new UpdateHostResponse(hostName, null)); @@ -44,7 +41,6 @@ public class OrchestratorImplTest { public void testSuspendCallWithFailureReason() { when(requestExecutor.put( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - port, Optional.empty(), UpdateHostResponse.class )).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail"))); @@ -56,7 +52,6 @@ public class OrchestratorImplTest { public void testSuspendCallWithNotFound() { when(requestExecutor.put( any(String.class), - any(Integer.class), any(), any() )).thenThrow(new HttpException.NotFoundException("Not Found")); @@ -68,7 +63,6 @@ public class OrchestratorImplTest { public void testSuspendCallWithSomeOtherException() { when(requestExecutor.put( any(String.class), - any(Integer.class), any(), any() )).thenThrow(new RuntimeException("Some parameter was wrong")); @@ -81,7 +75,6 @@ public class OrchestratorImplTest { public void testResumeCall() { when(requestExecutor.delete( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - port, UpdateHostResponse.class )).thenReturn(new UpdateHostResponse(hostName, null)); @@ -92,7 +85,6 @@ public class OrchestratorImplTest { public void testResumeCallWithFailureReason() { when(requestExecutor.delete( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended", - port, UpdateHostResponse.class )).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail"))); @@ -103,7 +95,6 @@ public class OrchestratorImplTest { public void testResumeCallWithNotFound() { when(requestExecutor.delete( any(String.class), - any(Integer.class), any() )).thenThrow(new HttpException.NotFoundException("Not Found")); @@ -114,7 +105,6 @@ public class OrchestratorImplTest { public void testResumeCallWithSomeOtherException() { when(requestExecutor.put( any(String.class), - any(Integer.class), any(), any() )).thenThrow(new RuntimeException("Some parameter was wrong")); @@ -130,7 +120,6 @@ public class OrchestratorImplTest { when(requestExecutor.put( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - port, Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), BatchOperationResult.class )).thenReturn(BatchOperationResult.successResult()); @@ -146,7 +135,6 @@ public class OrchestratorImplTest { when(requestExecutor.put( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - port, Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), BatchOperationResult.class )).thenReturn(new BatchOperationResult(failureReason)); @@ -162,7 +150,6 @@ public class OrchestratorImplTest { when(requestExecutor.put( OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API, - port, Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)), BatchOperationResult.class )).thenThrow(new RuntimeException(exceptionMessage)); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java index d431f175b1c..67cd2c79034 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java @@ -3,19 +3,22 @@ package com.yahoo.vespa.hosted.node.admin.util; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.yahoo.collections.ArraySet; -import org.apache.http.HttpEntity; -import org.apache.http.StatusLine; +import org.apache.http.HttpVersion; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicStatusLine; +import org.junit.Before; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.charset.StandardCharsets; -import java.util.Set; +import java.util.Arrays; +import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; @@ -38,85 +41,76 @@ public class ConfigServerHttpRequestExecutorTest { @JsonIgnoreProperties(ignoreUnknown = true) public static class TestPojo { @JsonProperty("foo") - public String foo; + String foo; @JsonProperty("error-code") - public Integer errorCode; + Integer errorCode; } + private final String uri1 = "http://host1:666"; + private final String uri2 = "http://host2:666"; + private final List<URI> configServers = Arrays.asList(URI.create(uri1), URI.create(uri2)); private final StringBuilder mockLog = new StringBuilder(); + + private ConfigServerHttpRequestExecutor executor; private int mockReturnCode = 200; - private CloseableHttpClient createClientMock() throws IOException { + @Before + public void initExecutor() throws IOException { CloseableHttpClient httpMock = mock(CloseableHttpClient.class); when(httpMock.execute(any())).thenAnswer(invocationOnMock -> { HttpGet get = (HttpGet) invocationOnMock.getArguments()[0]; mockLog.append(get.getMethod()).append(" ").append(get.getURI()).append(" "); - CloseableHttpResponse response = mock(CloseableHttpResponse.class); - StatusLine statusLine = mock(StatusLine.class); - when(statusLine.getStatusCode()).thenReturn(mockReturnCode); - when(response.getStatusLine()).thenReturn(statusLine); if (mockReturnCode == 100000) throw new RuntimeException("FAIL"); - HttpEntity entity = mock(HttpEntity.class); - when(response.getEntity()).thenReturn(entity); + + BasicStatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, mockReturnCode, null); + BasicHttpEntity entity = new BasicHttpEntity(); String returnMessage = "{\"foo\":\"bar\", \"no\":3, \"error-code\": " + mockReturnCode + "}"; InputStream stream = new ByteArrayInputStream(returnMessage.getBytes(StandardCharsets.UTF_8)); - when(entity.getContent()).thenReturn(stream); + entity.setContent(stream); + + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getEntity()).thenReturn(entity); + when(response.getStatusLine()).thenReturn(statusLine); + return response; }); doNothing().when(httpMock).close(); - return httpMock; + executor = new ConfigServerHttpRequestExecutor(configServers, httpMock); } @Test public void testBasicParsingSingleServer() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); - ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock()); - TestPojo answer = executor.get("/path", 666, TestPojo.class); + TestPojo answer = executor.get("/path", TestPojo.class); assertThat(answer.foo, is("bar")); assertLogStringContainsGETForAHost(); } @Test(expected = HttpException.class) public void testBasicFailure() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); // Server is returning 400, no retries. mockReturnCode = 400; - ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock()); - TestPojo testPojo = executor.get("/path", 666, TestPojo.class); + TestPojo testPojo = executor.get("/path", TestPojo.class); assertEquals(testPojo.errorCode.intValue(), mockReturnCode); assertLogStringContainsGETForAHost(); } @Test public void testBasicSuccessWithNoRetries() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); // Server is returning 201, no retries. mockReturnCode = 201; - ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock()); - TestPojo testPojo = executor.get("/path", 666, TestPojo.class); + TestPojo testPojo = executor.get("/path", TestPojo.class); assertEquals(testPojo.errorCode.intValue(), mockReturnCode); assertLogStringContainsGETForAHost(); } @Test public void testRetries() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); // Client is throwing exception, should be retries. mockReturnCode = 100000; - ConfigServerHttpRequestExecutor executor = - new ConfigServerHttpRequestExecutor(configServers, createClientMock()); try { - executor.get("/path", 666, TestPojo.class); + executor.get("/path", TestPojo.class); fail("Expected failure"); } catch (Exception e) { // ignore @@ -129,15 +123,10 @@ public class ConfigServerHttpRequestExecutorTest { @Test public void testRetriesOnBadHttpResponseCode() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); // Client is throwing exception, should be retries. mockReturnCode = 503; - ConfigServerHttpRequestExecutor executor = - new ConfigServerHttpRequestExecutor(configServers, createClientMock()); try { - executor.get("/path", 666, TestPojo.class); + executor.get("/path", TestPojo.class); fail("Expected failure"); } catch (Exception e) { // ignore @@ -151,14 +140,10 @@ public class ConfigServerHttpRequestExecutorTest { @Test public void testNotFound() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); // Server is returning 404, special exception is thrown. mockReturnCode = 404; - ConfigServerHttpRequestExecutor executor = new ConfigServerHttpRequestExecutor(configServers, createClientMock()); try { - executor.get("/path", 666, TestPojo.class); + executor.get("/path", TestPojo.class); fail("Expected exception"); } catch (HttpException.NotFoundException e) { // ignore @@ -168,14 +153,9 @@ public class ConfigServerHttpRequestExecutorTest { @Test public void testConflict() throws Exception { - Set<String> configServers = new ArraySet<>(2); - configServers.add("host1"); - configServers.add("host2"); // Server is returning 409, no exception is thrown. mockReturnCode = 409; - ConfigServerHttpRequestExecutor executor = - new ConfigServerHttpRequestExecutor(configServers, createClientMock()); - executor.get("/path", 666, TestPojo.class); + executor.get("/path", TestPojo.class); assertLogStringContainsGETForAHost(); } diff --git a/node-admin/src/test/resources/docker.stats.json b/node-admin/src/test/resources/docker.stats.json index 3b1087b9202..ff4a2fde943 100644 --- a/node-admin/src/test/resources/docker.stats.json +++ b/node-admin/src/test/resources/docker.stats.json @@ -36,7 +36,7 @@ 44567860460, 39049895962 ], - "usage_in_kernelmode":44050000000, + "usage_in_kernelmode":44106083850, "usage_in_usermode":158950000000 }, "system_cpu_usage":5876882680000000, diff --git a/node-admin/src/test/resources/expected.container.system.metrics.txt b/node-admin/src/test/resources/expected.container.system.metrics.txt index 8a4d696b08e..023d3958c60 100644 --- a/node-admin/src/test/resources/expected.container.system.metrics.txt +++ b/node-admin/src/test/resources/expected.container.system.metrics.txt @@ -11,6 +11,7 @@ s: "mem.limit": 4294967296, "mem.used": 1073741824, "disk.used": 39625000000, + "cpu.sys.util": 3.402, "disk.util": 15.85, "cpu.util": 5.4, "mem.util": 25.0, diff --git a/node-admin/vespa-node-admin.spec b/node-admin/vespa-node-admin.spec new file mode 100644 index 00000000000..049e66bf22f --- /dev/null +++ b/node-admin/vespa-node-admin.spec @@ -0,0 +1,49 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# Force special prefix for Vespa +%define _prefix /opt/vespa + +# Hack to speed up jar packing for now. This does not affect the rpm size. +%define __jar_repack %{nil} + +Name: vespa-node-admin +Version: %version +Release: 1%{?dist} +BuildArch: noarch +Summary: Vespa Node Admin +Group: Applications/Databases +License: Commercial +URL: http://vespa.ai + +Requires: bash +Requires: java-1.8.0-openjdk-headless +Requires: vespa-standalone-container + +Conflicts: vespa + +%description +The Node Admin manages the machine so it is a suitable host for one or more +Vespa nodes. + +%install +app_dir=%?buildroot%_prefix/conf/node-admin-app +mkdir -p "$app_dir"/components +cp node-admin/src/main/application/services.xml "$app_dir" + +declare -a jar_components=( + node-admin/target/node-admin-jar-with-dependencies.jar + docker-api/target/docker-api-jar-with-dependencies.jar +) +for path in "${jar_components[@]}"; do + cp "$path" "$app_dir"/components +done + +mkdir -p %buildroot%_prefix/libexec/vespa +cp node-admin/src/main/sh/node-admin.sh %buildroot%_prefix/libexec/vespa + +%clean +rm -rf %buildroot + +%files +%defattr(-,vespa,vespa,-) +%_prefix/* diff --git a/node-maintainer/pom.xml b/node-maintainer/pom.xml index d56e12d92c6..1fe3eb23857 100644 --- a/node-maintainer/pom.xml +++ b/node-maintainer/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>node-maintainer</artifactId> diff --git a/node-repository/pom.xml b/node-repository/pom.xml index 09b0d3df3d9..6673ea487a1 100644 --- a/node-repository/pom.xml +++ b/node-repository/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>node-repository</artifactId> <version>6-SNAPSHOT</version> diff --git a/node-repository/src/main/config/node-repository.xml b/node-repository/src/main/config/node-repository.xml index 72f9c44c8d8..d4b677cb0bb 100644 --- a/node-repository/src/main/config/node-repository.xml +++ b/node-repository/src/main/config/node-repository.xml @@ -11,6 +11,7 @@ <handler id="com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler" bundle="node-repository"> <binding>http://*/nodes/v2/*</binding> + <binding>https://*/nodes/v2/*</binding> </handler> <preprocess:include file="node-flavors.xml" required="false" /> diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index 88daa34d8ca..9b05478eb07 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -307,11 +307,16 @@ public class NodeRepository extends AbstractComponent { /** Sets a list of nodes ready and returns the nodes in the ready state */ public List<Node> setReady(List<Node> nodes) { - for (Node node : nodes) - if (node.state() != Node.State.dirty) - throw new IllegalArgumentException("Can not set " + node + " ready. It is not dirty."); try (Mutex lock = lockUnallocated()) { - return db.writeTo(Node.State.ready, nodes, Agent.system, Optional.empty()); + List<Node> nodesWithResetFields = nodes.stream() + .map(node -> { + if (node.state() != Node.State.dirty) + throw new IllegalArgumentException("Can not set " + node + " ready. It is not dirty."); + return node.with(node.status().withWantToRetire(false).withWantToDeprovision(false)); + }) + .collect(Collectors.toList()); + + return db.writeTo(Node.State.ready, nodesWithResetFields, Agent.system, Optional.empty()); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java index 500861ad0d2..6a2376d748b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java @@ -75,7 +75,7 @@ public abstract class ApplicationMaintainer extends Maintainer { // Lock is acquired with a low timeout to reduce the chance of colliding with an external deployment. try (Mutex lock = nodeRepository().lock(application, Duration.ofSeconds(1))) { if ( ! isActive(application)) return; // became inactive since deployment was requested - Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30)); + Optional<Deployment> deployment = deployer.deployFromLocalActive(application); if ( ! deployment.isPresent()) return; // this will be done at another config server deployment.get().activate(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java index 48ec71642c9..a3b4917147e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Expirer.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.maintenance; -import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.History; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java index 1e0202d4735..031d56e3164 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.maintenance; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeType; @@ -21,65 +22,108 @@ import java.util.stream.Collectors; /** * This moves expired failed nodes: * <ul> - * <li>To parked: If the node has known hardware failure, docker hosts are moved to parked only when all its + * <li>To parked: If the node has known hardware failure, Docker hosts are moved to parked only when all of their * children are already in parked - * <li>To dirty: If the node has failed less than 5 times OR the environment is dev, test or perf OR system is CD, - * as those environments have no protection against users running bogus applications, so + * <li>To dirty: If the node has failed less than 5 times OR the environment is dev, test or perf. + * Those environments have no protection against users running bogus applications, so * we cannot use the node failure count to conclude the node has a failure. * <li>Otherwise the node will remain in failed * </ul> - * Failed nodes are typically given a long expiry time to enable us to manually moved them back to + * Failed content nodes are given a long expiry time to enable us to manually moved them back to * active to recover data in cases where the node was failed accidentally. * <p> + * Failed container (Vespa, not Docker) nodes are expired early as there's no data to potentially recover. + * </p> + * <p> * The purpose of the automatic recycling to dirty + fail count is that nodes which were moved * to failed due to some undetected hardware failure will end up being failed again. * When that has happened enough they will not be recycled. * <p> - * The Chef recipe running locally on the node may set the hardwareFailureDescription to avoid the node + * The Chef recipe running locally on the node may set hardwareFailureDescription to avoid the node * being automatically recycled in cases where an error has been positively detected. * * @author bratseth + * @author mpolden */ -public class FailedExpirer extends Expirer { +public class FailedExpirer extends Maintainer { private static final Logger log = Logger.getLogger(NodeRetirer.class.getName()); + + private static final Duration defaultExpiry = Duration.ofDays(4); // Grace period to allow recovery of data + private static final Duration containerExpiry = Duration.ofHours(1); // Stateless nodes, no data to recover + private static final int maxAllowedFailures = 5; // Stop recycling nodes after this number of failures + private final NodeRepository nodeRepository; private final Zone zone; + private final Clock clock; - public FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock, - Duration failTimeout, JobControl jobControl) { - super(Node.State.failed, History.Event.Type.failed, nodeRepository, clock, failTimeout, jobControl); + public FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock, Duration interval, + JobControl jobControl) { + super(nodeRepository, interval, jobControl); this.nodeRepository = nodeRepository; this.zone = zone; + this.clock = clock; } @Override - protected void expire(List<Node> expired) { + protected void maintain() { + List<Node> containerNodes = getExpiredNodes(containerExpiry) + .stream() + .filter(node -> node.allocation().isPresent() && + node.allocation().get().membership().cluster().type() == ClusterSpec.Type.container) + .collect(Collectors.toList()); + List<Node> remainingNodes = getExpiredNodes(defaultExpiry); + remainingNodes.removeAll(containerNodes); + recycle(containerNodes); + recycle(remainingNodes); + } + + /** Get failed nodes that have expired according to given expiry */ + private List<Node> getExpiredNodes(Duration expiry) { + return nodeRepository.getNodes(Node.State.failed).stream() + .filter(node -> node.history().event(History.Event.Type.failed) + .map(event -> event.at().plus(expiry).isBefore(clock.instant())) + .orElse(false)) + .collect(Collectors.toList()); + } + + /** Move eligible nodes to dirty. This may be a subset of the given nodes */ + private void recycle(List<Node> nodes) { List<Node> nodesToRecycle = new ArrayList<>(); - for (Node recycleCandidate : expired) { - if (recycleCandidate.status().hardwareFailureDescription().isPresent() || recycleCandidate.status().hardwareDivergence().isPresent()) { - List<String> nonParkedChildren = recycleCandidate.type() != NodeType.host ? Collections.emptyList() : - nodeRepository.getChildNodes(recycleCandidate.hostname()).stream() + for (Node candidate : nodes) { + if (hasHardwareIssue(candidate)) { + List<String> unparkedChildren = candidate.type() != NodeType.host ? Collections.emptyList() : + nodeRepository.getChildNodes(candidate.hostname()).stream() .filter(node -> node.state() != Node.State.parked) .map(Node::hostname) .collect(Collectors.toList()); - if (nonParkedChildren.isEmpty()) { - nodeRepository.park(recycleCandidate.hostname(), Agent.system, "Parked by FailedExpirer due to HW failure/divergence on node"); + if (unparkedChildren.isEmpty()) { + nodeRepository.park(candidate.hostname(), Agent.system, + "Parked by FailedExpirer due to hardware issue"); } else { - log.info(String.format("Expired failed node %s with HW failure/divergence is not parked because some of its children" + - " (%s) are not yet parked", recycleCandidate.hostname(), String.join(", ", nonParkedChildren))); + log.info(String.format("Expired failed node %s with hardware issue was not parked because of " + + "unparked children: %s", candidate.hostname(), + String.join(", ", unparkedChildren))); } - } else if (! failCountIndicatesHwFail(zone, recycleCandidate) || recycleCandidate.status().failCount() < 5) { - nodesToRecycle.add(recycleCandidate); + } else if (!failCountIndicatesHardwareIssue(candidate)) { + nodesToRecycle.add(candidate); } } nodeRepository.setDirty(nodesToRecycle); } - private boolean failCountIndicatesHwFail(Zone zone, Node node) { + /** Returns whether the current node fail count should be used as an indicator of hardware issue */ + private boolean failCountIndicatesHardwareIssue(Node node) { if (node.flavor().getType() == Flavor.Type.DOCKER_CONTAINER) return false; - return zone.environment() == Environment.prod || zone.environment() == Environment.staging; + return (zone.environment() == Environment.prod || zone.environment() == Environment.staging) && + node.status().failCount() >= maxAllowedFailures; + } + + /** Returns whether node has any kind of hardware issue */ + private static boolean hasHardwareIssue(Node node) { + return node.status().hardwareFailureDescription().isPresent() || + node.status().hardwareDivergence().isPresent(); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index 1fffde874fd..9e826bfcb9a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -70,9 +70,9 @@ public class NodeRepositoryMaintenance extends AbstractComponent { zooKeeperAccessMaintainer = new ZooKeeperAccessMaintainer(nodeRepository, curator, durationFromEnv("zookeeper_access_maintenance_interval").orElse(defaults.zooKeeperAccessMaintenanceInterval), jobControl); reservationExpirer = new ReservationExpirer(nodeRepository, clock, durationFromEnv("reservation_expiry").orElse(defaults.reservationExpiry), jobControl); retiredExpirer = new RetiredExpirer(nodeRepository, deployer, clock, durationFromEnv("retired_expiry").orElse(defaults.retiredExpiry), jobControl); - retiredEarlyExpirer = new RetiredEarlyExpirer(nodeRepository, zone, durationFromEnv("retired_early_interval").orElse(defaults.retiredEarlyInterval), jobControl, deployer, orchestrator); + retiredEarlyExpirer = new RetiredEarlyExpirer(nodeRepository, durationFromEnv("retired_early_interval").orElse(defaults.retiredEarlyInterval), jobControl, deployer, orchestrator); inactiveExpirer = new InactiveExpirer(nodeRepository, clock, durationFromEnv("inactive_expiry").orElse(defaults.inactiveExpiry), jobControl); - failedExpirer = new FailedExpirer(nodeRepository, zone, clock, durationFromEnv("failed_expiry").orElse(defaults.failedExpiry), jobControl); + failedExpirer = new FailedExpirer(nodeRepository, zone, clock, durationFromEnv("failed_expirer_interval").orElse(defaults.failedExpirerInterval), jobControl); dirtyExpirer = new DirtyExpirer(nodeRepository, clock, durationFromEnv("dirty_expiry").orElse(defaults.dirtyExpiry), jobControl); provisionedExpirer = new ProvisionedExpirer(nodeRepository, clock, durationFromEnv("provisioned_expiry").orElse(defaults.provisionedExpiry), jobControl); nodeRebooter = new NodeRebooter(nodeRepository, clock, durationFromEnv("reboot_interval").orElse(defaults.rebootInterval), jobControl); @@ -134,7 +134,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { private final Duration reservationExpiry; private final Duration inactiveExpiry; private final Duration retiredExpiry; - private final Duration failedExpiry; + private final Duration failedExpirerInterval; private final Duration dirtyExpiry; private final Duration provisionedExpiry; private final Duration rebootInterval; @@ -156,7 +156,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { inactiveExpiry = Duration.ofHours(4); // enough time for the application owner to discover and redeploy retiredExpiry = Duration.ofDays(4); // enough time to migrate data retiredEarlyInterval = Duration.ofMinutes(29); - failedExpiry = Duration.ofDays(4); // enough time to recover data even if it happens friday night + failedExpirerInterval = Duration.ofMinutes(10); dirtyExpiry = Duration.ofHours(2); // enough time to clean the node provisionedExpiry = Duration.ofHours(4); rebootInterval = Duration.ofDays(30); @@ -174,7 +174,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { inactiveExpiry = Duration.ofSeconds(2); // support interactive wipe start over retiredExpiry = Duration.ofMinutes(1); retiredEarlyInterval = Duration.ofMinutes(5); - failedExpiry = Duration.ofMinutes(10); + failedExpirerInterval = Duration.ofMinutes(10); dirtyExpiry = Duration.ofMinutes(30); provisionedExpiry = Duration.ofHours(4); rebootInterval = Duration.ofDays(30); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java index 12b63f66d3f..30b5f6f737d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java @@ -123,7 +123,7 @@ public class NodeRetirer extends Maintainer { entry -> filterRetireableNodes(entry.getValue()))); if (retireableNodesByCluster.values().stream().mapToInt(Set::size).sum() == 0) continue; - Optional<Deployment> deployment = deployer.deployFromLocalActive(applicationId, Duration.ofMinutes(30)); + Optional<Deployment> deployment = deployer.deployFromLocalActive(applicationId); if ( ! deployment.isPresent()) continue; // this will be done at another config server Set<Node> replaceableNodes = retireableNodesByCluster.entrySet().stream() diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java index cb2fcb89284..00543058520 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredEarlyExpirer.java @@ -5,7 +5,6 @@ import com.yahoo.collections.ListMap; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Deployer; import com.yahoo.config.provision.Deployment; -import com.yahoo.config.provision.Zone; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; @@ -20,12 +19,19 @@ import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; +/** + * Maintenance job which deactivates retired nodes, if given permission by orchestrator. + * + * @author hakon + */ +// TODO: This should be consolidated with RetiredExpirer. The only difference between this and RetiredExpirer is that +// this runs more often by default and asks orchestrator for permission to retire nodes. public class RetiredEarlyExpirer extends Maintainer { + private final Deployer deployer; private final Orchestrator orchestrator; public RetiredEarlyExpirer(NodeRepository nodeRepository, - Zone zone, Duration interval, JobControl jobControl, Deployer deployer, @@ -51,12 +57,12 @@ public class RetiredEarlyExpirer extends Maintainer { List<Node> retiredNodes = entry.getValue(); try { - Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30)); + Optional<Deployment> deployment = deployer.deployFromLocalActive(application); if ( ! deployment.isPresent()) continue; // this will be done at another config server List<Node> nodesToRemove = new ArrayList<>(); for (Node node : retiredNodes) { - if (nodeCanBeRemoved(node)) { + if (canRemove(node)) { nodesToRemove.add(node); } } @@ -79,7 +85,8 @@ public class RetiredEarlyExpirer extends Maintainer { } } - boolean nodeCanBeRemoved(Node node) { + /** Returns whether orchestrator permits given node to be removed */ + private boolean canRemove(Node node) { try { orchestrator.acquirePermissionToRemove(new HostName(node.hostname())); return true; @@ -88,4 +95,5 @@ public class RetiredEarlyExpirer extends Maintainer { return false; } } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java index 9ef09858bfa..4c5c8adf576 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirer.java @@ -50,7 +50,7 @@ public class RetiredExpirer extends Expirer { ApplicationId application = entry.getKey(); List<Node> nodesToRemove = entry.getValue(); try { - Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30)); + Optional<Deployment> deployment = deployer.deployFromLocalActive(application); if ( ! deployment.isPresent()) continue; // this will be done at another config server nodeRepository.setRemovable(application, nodesToRemove); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index 1de15f4e31a..7ef609d6311 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -52,7 +52,7 @@ public class NodeRepositoryProvisioner implements Provisioner { @Inject public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone) { - this(nodeRepository, flavors, zone, Clock.systemUTC(), (x,y) -> {}); + this(nodeRepository, flavors, zone, Clock.systemUTC(), (x, y) -> {}); } public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone, Clock clock, BiConsumer<List<Node>, String> debugRecorder) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java index 797453b12c9..b47b3544d17 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java @@ -184,8 +184,7 @@ public class NodesApiHandler extends LoggingRequestHandler { } private Node nodeFromRequest(HttpRequest request) { - String path = request.getUri().getPath(); - String hostname = path.substring(path.lastIndexOf("/")); + String hostname = lastElement(request.getUri().getPath()); return nodeRepository.getNode(hostname).orElseThrow(() -> new NotFoundException("No node found with hostname " + hostname)); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java index a05b0be344d..c880c66abc7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java @@ -46,6 +46,11 @@ public class MockDeployer implements Deployer { } @Override + public Optional<Deployment> deployFromLocalActive(ApplicationId application) { + return deployFromLocalActive(application, Duration.ofSeconds(60)); + } + + @Override public Optional<Deployment> deployFromLocalActive(ApplicationId id, Duration timeout) { return Optional.of(new MockDeployment(provisioner, applications.get(id))); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java index bcc18cfd876..928d8082c6b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java @@ -45,8 +45,12 @@ public class MockNodeRepository extends NodeRepository { */ public MockNodeRepository(MockCurator curator, NodeFlavors flavors) throws Exception { super(flavors, curator, Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), Zone.defaultZone(), - new MockNameResolver().mockAnyLookup(), new DockerImage("docker-registry.domain.tld:8080/dist/vespa")); + new MockNameResolver() + .addRecord("test-container-1", "::2") + .mockAnyLookup(), + new DockerImage("docker-registry.domain.tld:8080/dist/vespa")); this.flavors = flavors; + curator.setConnectionSpec("cfg1:1234,cfg2:1234,cfg3:1234"); populate(); } @@ -91,9 +95,10 @@ public class MockNodeRepository extends NodeRepository { node10 = node10.with(node10newStatus); nodes.add(node10); - nodes.add(createNode("node55", "host55.yahoo.com", ipAddresses, Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant)); + Node node55 = createNode("node55", "host55.yahoo.com", ipAddresses, Optional.empty(), flavors.getFlavorOrThrow("default"), NodeType.tenant); + nodes.add(node55.with(node55.status().withWantToRetire(true).withWantToDeprovision(true))); - /** Setup docker hosts (two of these will be reserved for spares */ + /* Setup docker hosts (two of these will be reserved for spares */ nodes.add(createNode("dockerhost1", "dockerhost1.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); nodes.add(createNode("dockerhost2", "dockerhost2.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); nodes.add(createNode("dockerhost3", "dockerhost3.yahoo.com", ipAddresses, additionalIpAddresses, Optional.empty(), flavors.getFlavorOrThrow("large"), NodeType.host)); @@ -133,4 +138,4 @@ public class MockNodeRepository extends NodeRepository { provisioner.activate(transaction, application, hosts); transaction.commit(); } -}
\ No newline at end of file +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java index 51991a844d7..720c5b05443 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.Flavor; +import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeType; @@ -18,7 +19,6 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; import com.yahoo.test.ManualClock; import com.yahoo.transaction.NestedTransaction; -import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.curator.transaction.CuratorTransaction; import com.yahoo.vespa.hosted.provision.Node; @@ -26,15 +26,15 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; -import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import org.junit.Test; import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -42,155 +42,268 @@ import static org.junit.Assert.assertEquals; /** * @author bratseth + * @author mpolden */ public class FailedExpirerTest { - private final Curator curator = new MockCurator(); - private final ManualClock clock = new ManualClock(); - private FailedExpirer failedExpirer; - @Test - public void ensure_failed_nodes_are_deallocated_in_prod() throws InterruptedException { - failureScenarioIn(SystemName.main, Environment.prod, "default"); - clock.advance(Duration.ofDays(5)); - failedExpirer.run(); - - assertNodeHostnames(Node.State.failed, "node1"); - assertNodeHostnames(Node.State.parked, "node2", "node3"); + public void ensure_failed_nodes_are_deallocated_in_prod() { + FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod) + .withNode("node1") + .withNode("node2") + .withNode("node3") + .setReady("node1", "node2", "node3") + .allocate(ClusterSpec.Type.content, "node1", "node2", "node3") + .failNode(4, "node1") + .failWithHardwareFailure("node2", "node3"); + + scenario.clock().advance(Duration.ofDays(3)); + scenario.expirer().run(); + scenario.assertNodesIn(Node.State.failed, "node1", "node2", "node3"); // None moved yet + + scenario.clock().advance(Duration.ofDays(2)); + scenario.expirer().run(); + scenario.assertNodesIn(Node.State.failed, "node1"); + scenario.assertNodesIn(Node.State.parked, "node2", "node3"); } @Test - public void ensure_failed_nodes_are_deallocated_in_dev() throws InterruptedException { - failureScenarioIn(SystemName.main, Environment.dev, "default"); - clock.advance(Duration.ofDays(5)); - failedExpirer.run(); - - assertNodeHostnames(Node.State.parked, "node2", "node3"); - assertNodeHostnames(Node.State.dirty, "node1"); + public void ensure_failed_nodes_are_deallocated_in_dev() { + FailureScenario scenario = new FailureScenario(SystemName.main, Environment.dev) + .withNode("node1") + .withNode("node2") + .withNode("node3") + .setReady("node1", "node2", "node3") + .allocate(ClusterSpec.Type.content, "node1", "node2", "node3") + .failNode(4, "node1") + .failWithHardwareFailure("node2", "node3"); + + scenario.clock().advance(Duration.ofDays(5)); + scenario.expirer().run(); + + scenario.assertNodesIn(Node.State.parked, "node2", "node3"); + scenario.assertNodesIn(Node.State.dirty, "node1"); } @Test - public void ensure_failed_nodes_are_deallocated_in_cd() throws InterruptedException { - failureScenarioIn(SystemName.cd, Environment.prod, "default"); - clock.advance(Duration.ofDays(5)); - failedExpirer.run(); - - assertNodeHostnames(Node.State.failed, "node1"); - assertNodeHostnames(Node.State.parked, "node2", "node3"); + public void ensure_failed_nodes_are_deallocated_in_cd() { + FailureScenario scenario = new FailureScenario(SystemName.cd, Environment.prod) + .withNode("node1") + .withNode("node2") + .withNode("node3") + .setReady("node1", "node2", "node3") + .allocate(ClusterSpec.Type.content, "node1", "node2", "node3") + .failNode(4, "node1") + .failWithHardwareFailure("node2", "node3"); + + scenario.clock().advance(Duration.ofDays(5)); + scenario.expirer().run(); + + scenario.assertNodesIn(Node.State.failed, "node1"); + scenario.assertNodesIn(Node.State.parked, "node2", "node3"); } @Test - public void ensure_failed_docker_nodes_are_deallocated() throws InterruptedException { - failureScenarioIn(SystemName.main, Environment.prod, "docker"); - clock.advance(Duration.ofDays(5)); - failedExpirer.run(); - - assertNodeHostnames(Node.State.parked, "node2", "node3"); - assertNodeHostnames(Node.State.dirty, "node1"); + public void ensure_failed_docker_nodes_are_deallocated() { + FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod) + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent1") + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent2") + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent3") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node1", "parent1") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node2", "parent2") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node3", "parent3") + .setReady("node1", "node2", "node3") + .allocate(ClusterSpec.Type.content, FailureScenario.dockerFlavor, "node1", "node2", "node3") + .failNode(4, "node1") + .failWithHardwareFailure("node2", "node3"); + + scenario.clock().advance(Duration.ofDays(5)); + scenario.expirer().run(); + + scenario.assertNodesIn(Node.State.parked, "node2", "node3"); + scenario.assertNodesIn(Node.State.dirty, "node1"); } @Test - public void ensure_parked_docker_host() throws InterruptedException { - failureScenarioIn(SystemName.main, Environment.prod, "docker"); - - failNode("parent2"); - setHWFailureForNode("parent2"); - - clock.advance(Duration.ofDays(5)); - failedExpirer.run(); // Run twice because parent can only be parked after the child - failedExpirer.run(); - - assertNodeHostnames(Node.State.parked, "parent2", "node2", "node3"); + public void ensure_parked_docker_host() { + FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod) + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent1") + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent2") + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent3") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node1", "parent1") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node2", "parent2") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node3", "parent3") + .setReady("node1", "node2", "node3") + .allocate(ClusterSpec.Type.content, FailureScenario.dockerFlavor, "node1", "node2", "node3") + .failNode(8, "node3") + .failWithHardwareFailure("node2", "node3") + .failWithHardwareFailure("parent2"); + + scenario.clock.advance(Duration.ofDays(5)); + scenario.expirer().run(); // Run twice because parent can only be parked after the child + scenario.expirer().run(); + scenario.assertNodesIn(Node.State.parked, "parent2", "node2", "node3"); } @Test - public void ensure_failed_docker_host_is_not_parked_unless_all_children_are() throws InterruptedException { - failureScenarioIn(SystemName.cd, Environment.prod, "docker"); - - failNode("parent1"); - setHWFailureForNode("parent1"); - clock.advance(Duration.ofDays(2)); - failNode("node4"); - failNode("node5"); - clock.advance(Duration.ofDays(3)); - - failedExpirer.run(); // Run twice because parent can only be parked after the child - failedExpirer.run(); - - assertNodeHostnames(Node.State.failed, "parent1", "node4", "node5"); + public void ensure_failed_docker_host_is_not_parked_unless_all_children_are() { + FailureScenario scenario = new FailureScenario(SystemName.cd, Environment.prod) + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent1") + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent2") + .withNode(NodeType.host, FailureScenario.defaultFlavor, "parent3") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node1", "parent1") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node2", "parent2") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node3", "parent3") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node4", "parent1") + .withNode(NodeType.tenant, FailureScenario.dockerFlavor, "node5", "parent1") + .setReady("node1", "node2", "node3") + .allocate(ClusterSpec.Type.content, FailureScenario.dockerFlavor, "node1", "node2", "node3") + .failWithHardwareFailure("parent1"); + + scenario.clock().advance(Duration.ofDays(2)); + scenario.failNode(1, "node4", "node5"); + scenario.clock().advance(Duration.ofDays(3)); + + scenario.expirer().run(); // Run twice because parent can only be parked after the child + scenario.expirer().run(); + + scenario.assertNodesIn(Node.State.failed, "parent1", "node4", "node5"); } - private void assertNodeHostnames(Node.State state, String... hostnames) { - assertEquals(Stream.of(hostnames).collect(Collectors.toSet()), - failedExpirer.nodeRepository().getNodes(state).stream().map(Node::hostname).collect(Collectors.toSet())); - } - - private void setHWFailureForNode(String hostname) { - Node node2 = failedExpirer.nodeRepository().getNode(hostname).get(); - node2 = node2.with(node2.status().withHardwareFailureDescription(Optional.of("memory_mcelog"))); - failedExpirer.nodeRepository().write(node2); + @Test + public void ensure_container_nodes_are_recycled_early() { + FailureScenario scenario = new FailureScenario(SystemName.main, Environment.prod) + .withNode("node1") + .withNode("node2") + .withNode("node3") + .withNode("node4") + .withNode("node5") + .withNode("node6") + .setReady("node1", "node2", "node3", "node4", "node5", "node6") + .allocate(ClusterSpec.Type.content, "node1", "node2", "node3") + .allocate(ClusterSpec.Type.container, "node4", "node5", "node6"); + + // Vespa container fails + scenario.failNode(1, "node4"); + + // 30 minutes pass, nothing happens + scenario.clock().advance(Duration.ofMinutes(30)); + scenario.expirer().run(); + scenario.assertNodesIn(Node.State.dirty); + + // Recycles container when more than 1 hour passes + scenario.clock().advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1))); + scenario.expirer().run(); + scenario.assertNodesIn(Node.State.dirty, "node4"); } - private void failNode(String hostname) { - failedExpirer.nodeRepository().fail(hostname, Agent.system, "Failing to unit test"); + private static class FailureScenario { + + private static final NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default", "docker"); + public static final Flavor defaultFlavor = nodeFlavors.getFlavorOrThrow("default"); + public static final Flavor dockerFlavor = nodeFlavors.getFlavorOrThrow("docker"); + + private final MockCurator curator = new MockCurator(); + private final ManualClock clock = new ManualClock(); + private final ApplicationId applicationId = ApplicationId.from(TenantName.from("foo"), + ApplicationName.from("bar"), + InstanceName.from("default")); + + private final NodeRepository nodeRepository; + private final NodeRepositoryProvisioner provisioner; + private final FailedExpirer expirer; + + public FailureScenario(SystemName system, Environment environment) { + Zone zone = new Zone(system, environment, RegionName.defaultName()); + this.nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone, + new MockNameResolver().mockAnyLookup(), + new DockerImage("docker-image")); + this.provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), clock, + (x, y) -> {}); + this.expirer = new FailedExpirer(nodeRepository, zone, clock, Duration.ofMinutes(30), + new JobControl(nodeRepository.database())); + } + + public ManualClock clock() { + return clock; + } + + public FailedExpirer expirer() { + return expirer; + } + + public Node get(String hostname) { + return nodeRepository.getNode(hostname) + .orElseThrow(() -> new IllegalArgumentException("No such node: " + hostname)); + } + + public FailureScenario withNode(NodeType type, Flavor flavor, String hostname, String parentHostname) { + nodeRepository.addNodes(Collections.singletonList( + nodeRepository.createNode(UUID.randomUUID().toString(), hostname, + Optional.ofNullable(parentHostname), flavor, type) + )); + return this; + } + + public FailureScenario withNode(NodeType type, Flavor flavor, String hostname) { + return withNode(type, flavor, hostname,null); + } + + public FailureScenario withNode(String hostname) { + return withNode(NodeType.tenant, defaultFlavor, hostname, null); + } + + public FailureScenario failNode(int times, String... hostname) { + Stream.of(hostname).forEach(h -> { + Node node = get(h); + nodeRepository.write(node.with(node.status().setFailCount(times))); + nodeRepository.fail(h, Agent.system, "Failed by unit test"); + }); + return this; + } + + public FailureScenario failWithHardwareFailure(String... hostname) { + Stream.of(hostname).forEach(h -> { + Node node = get(h); + nodeRepository.write(node.with(node.status().withHardwareFailureDescription( + Optional.of("memory_mcelog")))); + nodeRepository.fail(h, Agent.system, "Failed by unit test"); + }); + return this; + } + + public FailureScenario setReady(String... hostname) { + List<Node> nodes = Stream.of(hostname) + .map(this::get) + .collect(Collectors.toList()); + nodeRepository.setReady(nodeRepository.setDirty(nodes)); + return this; + } + + public FailureScenario allocate(ClusterSpec.Type clusterType, String... hostname) { + return allocate(clusterType, defaultFlavor, hostname); + } + + public FailureScenario allocate(ClusterSpec.Type clusterType, Flavor flavor, String... hostname) { + Set<HostSpec> hosts = Stream.of(hostname) + .map(h -> new HostSpec(h, Optional.empty())) + .collect(Collectors.toSet()); + ClusterSpec clusterSpec = ClusterSpec.request(clusterType, ClusterSpec.Id.from("test"), + Version.fromString("6.42")); + provisioner.prepare(applicationId, clusterSpec, Capacity.fromNodeCount(hostname.length, flavor.name()), + 1, null); + NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator)); + provisioner.activate(transaction, applicationId, hosts); + transaction.commit(); + return this; + } + + public void assertNodesIn(Node.State state, String... hostnames) { + assertEquals(Stream.of(hostnames).collect(Collectors.toSet()), + nodeRepository.getNodes(state).stream() + .map(Node::hostname) + .collect(Collectors.toSet())); + } } - private void failureScenarioIn(SystemName system, Environment environment, String flavorName) { - NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default", flavorName); - NodeRepository nodeRepository = new NodeRepository(nodeFlavors, curator, clock, Zone.defaultZone(), - new MockNameResolver().mockAnyLookup(), - new DockerImage("docker-registry.domain.tld:8080/dist/vespa")); - NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, Zone.defaultZone(), clock, (x,y) -> {}); - failedExpirer = new FailedExpirer(nodeRepository, new Zone(system, environment, RegionName.from("us-west-1")), clock, Duration.ofDays(4), new JobControl(nodeRepository.database())); - - Flavor defaultFlavor = nodeFlavors.getFlavorOrThrow("default"); - List<Node> hostNodes = new ArrayList<>(3); - hostNodes.add(nodeRepository.createNode("parent1", "parent1", Optional.empty(), defaultFlavor, NodeType.host)); - hostNodes.add(nodeRepository.createNode("parent2", "parent2", Optional.empty(), defaultFlavor, NodeType.host)); - hostNodes.add(nodeRepository.createNode("parent3", "parent3", Optional.empty(), defaultFlavor, NodeType.host)); - nodeRepository.addNodes(hostNodes); - - Flavor flavor = nodeFlavors.getFlavorOrThrow(flavorName); - List<Node> nodes = new ArrayList<>(3); - Optional<String> parentHost1 = flavorName.equals("docker") ? Optional.of("parent1") : Optional.empty(); - Optional<String> parentHost2 = flavorName.equals("docker") ? Optional.of("parent2") : Optional.empty(); - Optional<String> parentHost3 = flavorName.equals("docker") ? Optional.of("parent3") : Optional.empty(); - nodes.add(nodeRepository.createNode("node1", "node1", parentHost1, flavor, NodeType.tenant)); - nodes.add(nodeRepository.createNode("node2", "node2", parentHost2, flavor, NodeType.tenant)); - nodes.add(nodeRepository.createNode("node3", "node3", parentHost3, flavor, NodeType.tenant)); - nodeRepository.addNodes(nodes); - - // Set node1 to have failed 4 times before - Node node1 = nodeRepository.getNode("node1").get(); - node1 = node1.with(node1.status().setFailCount(4)); - nodeRepository.write(node1); - - // Set node2 to have a detected hardware failure - setHWFailureForNode("node2"); - - // Set node3 to have failed 8 times before and have a HW failure - Node node3 = nodeRepository.getNode("node3").get(); - node3 = node1.with(node3.status().setFailCount(8)); - nodeRepository.write(node3); - setHWFailureForNode("node3"); - - // Allocate the nodes - List<Node> provisioned = nodeRepository.getNodes(NodeType.tenant, Node.State.provisioned); - nodeRepository.setReady(nodeRepository.setDirty(provisioned)); - nodeRepository.addNodes(Arrays.asList( - nodeRepository.createNode("node4", "node4", parentHost1, flavor, NodeType.tenant), - nodeRepository.createNode("node5", "node5", parentHost1, flavor, NodeType.tenant))); - - ApplicationId applicationId = ApplicationId.from(TenantName.from("foo"), ApplicationName.from("bar"), InstanceName.from("fuz")); - ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test"), Version.fromString("6.42")); - provisioner.prepare(applicationId, cluster, Capacity.fromNodeCount(3, flavorName), 1, null); - NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator)); - provisioner.activate(transaction, applicationId, ProvisioningTester.toHostSpecs(nodes)); - transaction.commit(); - assertEquals(3, nodeRepository.getNodes(NodeType.tenant, Node.State.active).size()); - - // Fail the nodes - nodes.forEach(node -> failNode(node.hostname())); - assertEquals(3, nodeRepository.getNodes(NodeType.tenant, Node.State.failed).size()); - } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java index 704ded54479..048856bc698 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java @@ -40,7 +40,7 @@ public class InactiveAndFailedExpirerTest { InstanceName.from("fuz")); @Test - public void inactive_and_failed_times_out() throws InterruptedException { + public void inactive_and_failed_times_out() { ProvisioningTester tester = new ProvisioningTester(new Zone(Environment.prod, RegionName.from("us-east"))); List<Node> nodes = tester.makeReadyNodes(2, "default"); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java index 8195321be03..12e2eb3f323 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java @@ -52,7 +52,7 @@ public class RetiredExpirerTest { private Curator curator = new MockCurator(); @Test - public void ensure_retired_nodes_time_out() throws InterruptedException { + public void ensure_retired_nodes_time_out() { ManualClock clock = new ManualClock(); Zone zone = new Zone(Environment.prod, RegionName.from("us-east")); NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default"); @@ -92,7 +92,7 @@ public class RetiredExpirerTest { } @Test - public void ensure_retired_groups_time_out() throws InterruptedException { + public void ensure_retired_groups_time_out() { ManualClock clock = new ManualClock(); Zone zone = new Zone(Environment.prod, RegionName.from("us-east")); NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default"); @@ -170,7 +170,6 @@ public class RetiredExpirerTest { new RetiredEarlyExpirer( nodeRepository, - zone, Duration.ofDays(30), new JobControl(nodeRepository.database()), deployer, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java index 37b2f54da4d..c23a7f9990a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java @@ -13,7 +13,6 @@ import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.provision.testutils.ContainerConfig; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import java.io.File; @@ -40,9 +39,20 @@ public class RestApiTest { private final static String responsesPath = "src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/"; + private JDisc container; + + @Before + public void startContainer() { + container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(0), Networking.disable); + } + + @After + public void stopContainer() { + container.close(); + } + /** This test gives examples of all the requests that can be made to nodes/v2 */ @Test - @Ignore /** TODO re-enable this and verify correctness */ public void test_requests() throws Exception { // GET assertFile(new Request("http://localhost:8080/nodes/v2/"), "root.json"); @@ -54,28 +64,28 @@ public class RestApiTest { assertFile(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"), "node2.json"); // GET with filters - assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&hostname=host2.yahoo.com%20host1.yahoo.com"), "application2-nodes.json"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&clusterType=content"), "active-nodes.json"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&hostname=host6.yahoo.com%20host2.yahoo.com"), "application2-nodes.json"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&clusterType=content"), "content-nodes.json"); assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&clusterId=id2"), "application2-nodes.json"); assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&application=tenant2.application2.instance2"), "application2-nodes.json"); - assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&parentHost=parent1.yahoo.com,parent.host.yahoo.com"), "parent-nodes.json"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/?recursive=true&parentHost=dockerhost1.yahoo.com"), "child-nodes.json"); // POST restart command assertRestart(1, new Request("http://localhost:8080/nodes/v2/command/restart?hostname=host2.yahoo.com", new byte[0], Request.Method.POST)); assertRestart(2, new Request("http://localhost:8080/nodes/v2/command/restart?application=tenant2.application2.instance2", new byte[0], Request.Method.POST)); - assertRestart(4, new Request("http://localhost:8080/nodes/v2/command/restart", + assertRestart(9, new Request("http://localhost:8080/nodes/v2/command/restart", new byte[0], Request.Method.POST)); assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"), "\"restartGeneration\":3"); // POST reboot command - assertReboot(5, new Request("http://localhost:8080/nodes/v2/command/reboot?state=failed%20active", + assertReboot(10, new Request("http://localhost:8080/nodes/v2/command/reboot?state=failed%20active", new byte[0], Request.Method.POST)); assertReboot(2, new Request("http://localhost:8080/nodes/v2/command/reboot?application=tenant2.application2.instance2", new byte[0], Request.Method.POST)); - assertReboot(10, new Request("http://localhost:8080/nodes/v2/command/reboot", + assertReboot(15, new Request("http://localhost:8080/nodes/v2/command/reboot", new byte[0], Request.Method.POST)); assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"), "\"rebootGeneration\":4"); @@ -106,9 +116,9 @@ public class RestApiTest { assertFile(new Request("http://localhost:8080/nodes/v2/node/parent2.yahoo.com"), "parent2.json"); // DELETE a provisioned node - assertResponse(new Request("http://localhost:8080/nodes/v2/node/host11.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/node/host9.yahoo.com", new byte[0], Request.Method.DELETE), - "{\"message\":\"Removed host11.yahoo.com\"}"); + "{\"message\":\"Removed host9.yahoo.com\"}"); // PUT nodes ready assertResponse(new Request("http://localhost:8080/nodes/v2/state/dirty/host8.yahoo.com", @@ -125,15 +135,15 @@ public class RestApiTest { "{\"message\":\"Moved host8.yahoo.com to ready\"}"); // PUT a node in failed ... - assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/host3.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/host2.yahoo.com", new byte[0], Request.Method.PUT), - "{\"message\":\"Moved host3.yahoo.com to failed\"}"); - assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host3.yahoo.com"), + "{\"message\":\"Moved host2.yahoo.com to failed\"}"); + assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host2.yahoo.com"), "\"state\":\"failed\""); // ... and put it back in active (after fixing). This is useful to restore data when multiple nodes fail. - assertResponse(new Request("http://localhost:8080/nodes/v2/state/active/host3.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/state/active/host2.yahoo.com", new byte[0], Request.Method.PUT), - "{\"message\":\"Moved host3.yahoo.com to active\"}"); + "{\"message\":\"Moved host2.yahoo.com to active\"}"); // PUT a node in parked ... assertResponse(new Request("http://localhost:8080/nodes/v2/state/parked/host8.yahoo.com", @@ -147,30 +157,29 @@ public class RestApiTest { "{\"message\":\"Removed host8.yahoo.com\"}"); // or, PUT a node in failed ... - assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/host6.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/test-container-1", new byte[0], Request.Method.PUT), - "{\"message\":\"Moved host6.yahoo.com to failed\"}"); - assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), + "{\"message\":\"Moved test-container-1 to failed\"}"); + assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/test-container-1"), "\"state\":\"failed\""); // ... and deallocate it such that it moves to dirty and is recycled - assertResponse(new Request("http://localhost:8080/nodes/v2/state/dirty/host6.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/state/dirty/test-container-1", new byte[0], Request.Method.PUT), - "{\"message\":\"Moved host6.yahoo.com to dirty\"}"); + "{\"message\":\"Moved test-container-1 to dirty\"}"); // ... and set it back to ready as if this was from the node-admin with the temporary state rest api - assertResponse(new Request("http://localhost:8080/nodes/v2/state/availablefornewallocations/host6.yahoo.com", + assertResponse(new Request("http://localhost:8080/nodes/v2/state/availablefornewallocations/test-container-1", new byte[0], Request.Method.PUT), - "{\"message\":\"Moved host6.yahoo.com to ready\"}"); + "{\"message\":\"Marked following nodes as available for new allocation: test-container-1\"}"); // Put a host in failed and make sure it's children are also failed - assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/parent1.yahoo.com", new byte[0], Request.Method.PUT), - "{\"message\":\"Moved host10.yahoo.com, host5.yahoo.com, parent1.yahoo.com to failed\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed/dockerhost1.yahoo.com", new byte[0], Request.Method.PUT), + "{\"message\":\"Moved dockerhost1.yahoo.com, host4.yahoo.com to failed\"}"); assertResponse(new Request("http://localhost:8080/nodes/v2/state/failed"), "{\"nodes\":[" + - "{\"url\":\"http://localhost:8080/nodes/v2/node/parent1.yahoo.com\"}," + + "{\"url\":\"http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com\"}," + "{\"url\":\"http://localhost:8080/nodes/v2/node/host5.yahoo.com\"}," + - "{\"url\":\"http://localhost:8080/nodes/v2/node/host10.yahoo.com\"}]}"); - + "{\"url\":\"http://localhost:8080/nodes/v2/node/host4.yahoo.com\"}]}"); // Update (PATCH) a node (multiple fields can also be sent in one request body) assertResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com", @@ -299,6 +308,15 @@ public class RestApiTest { } @Test + public void setting_node_to_ready_will_reset_certain_fields() throws Exception { + final String hostname = "host55.yahoo.com"; + assertResponse(new Request("http://localhost:8080/nodes/v2/state/ready/" + hostname, + new byte[0], Request.Method.PUT), + "{\"message\":\"Moved " + hostname + " to ready\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/" + hostname), "node55-after-changes.json"); + } + + @Test public void acl_request_by_tenant_node() throws Exception { String hostname = "foo.yahoo.com"; assertResponse(new Request("http://localhost:8080/nodes/v2/node", @@ -437,6 +455,11 @@ public class RestApiTest { Request.Method.PATCH), "{\"message\":\"Updated host4.yahoo.com\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/node/doesnotexist.yahoo.com", + Utf8.toBytes("{\"currentRestartGeneration\": 1}"), + Request.Method.PATCH), + 404, "{\"error-code\":\"NOT_FOUND\",\"message\":\"No node found with hostname doesnotexist.yahoo.com\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/node/host5.yahoo.com", Utf8.toBytes("{\"currentRestartGeneration\": 1}"), Request.Method.PATCH), @@ -514,13 +537,6 @@ public class RestApiTest { } } - private JDisc container; - @Before - public void startContainer() { - container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(0), Networking.disable); } - @After - public void stopContainer() { container.close(); } - private String asDockerNodeJson(String hostname, String parentHostname, int additionalIpCount, String... ipAddress) { return "{\"hostname\":\"" + hostname + "\", \"parentHostname\":\"" + parentHostname + "\"," + createIpAddresses(ipAddress) + diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json index d1df5b83f24..c67ba904f9a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/active-nodes.json @@ -1,8 +1,13 @@ { "nodes": [ + @include(docker-node4.json), + @include(docker-node5.json), + @include(docker-node2.json), + @include(docker-node1.json), + @include(docker-node3.json), @include(node6.json), - @include(node3.json), @include(node2.json), - @include(node1.json) + @include(docker-container1.json), + @include(node4.json) ] -}
\ No newline at end of file +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json index 1d4d97315cd..4581ecba73d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/application2-nodes.json @@ -1,6 +1,6 @@ { "nodes": [ - @include(node2.json), - @include(node1.json) + @include(node6.json), + @include(node2.json) ] -}
\ No newline at end of file +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json new file mode 100644 index 00000000000..dae92aae091 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/child-nodes.json @@ -0,0 +1,5 @@ +{ + "nodes": [ + @include(node4.json) + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json new file mode 100644 index 00000000000..47a2c012b17 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/content-nodes.json @@ -0,0 +1,8 @@ +{ + "nodes": [ + @include(node6.json), + @include(node2.json), + @include(docker-container1.json), + @include(node4.json) + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json new file mode 100644 index 00000000000..7823ed0431d --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-container1.json @@ -0,0 +1,56 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/test-container-1", + "id": "test-container-1", + "state": "active", + "type": "tenant", + "hostname": "test-container-1", + "parentHostname": "dockerhost3.yahoo.com", + "openStackId": "fake-test-container-1", + "flavor": "docker", + "canonicalFlavor": "docker", + "minDiskAvailableGb": 100.0, + "minMainMemoryAvailableGb": 0.5, + "description": "Flavor-name-is-docker", + "minCpuCores": 0.2, + "fastDisk": true, + "environment": "DOCKER_CONTAINER", + "owner": { + "tenant": "tenant3", + "application": "application3", + "instance": "instance3" + }, + "membership": { + "clustertype": "content", + "clusterid": "id3", + "group": "0", + "index": 1, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "allowedToBeDown": false, + "rebootGeneration": 0, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "reserved", + "at": 123, + "agent": "system" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + } + ], + "ipAddresses": [ + "::2" + ], + "additionalIpAddresses": [] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json new file mode 100644 index 00000000000..a13dfae927f --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node1.json @@ -0,0 +1,70 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com", + "id": "dockerhost1.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost1.yahoo.com", + "openStackId": "dockerhost1", + "flavor": "large", + "canonicalFlavor": "large", + "minDiskAvailableGb": 1600.0, + "minMainMemoryAvailableGb": 32.0, + "description": "Flavor-name-is-large", + "minCpuCores": 4.0, + "fastDisk": true, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 0, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "allowedToBeDown": false, + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + } + ], + "ipAddresses": [ + "::1", + "127.0.0.1" + ], + "additionalIpAddresses": [ + "::2", + "::3", + "::4" + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json new file mode 100644 index 00000000000..f7a1d6ab9a9 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node2.json @@ -0,0 +1,70 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost2.yahoo.com", + "id": "dockerhost2.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost2.yahoo.com", + "openStackId": "dockerhost2", + "flavor": "large", + "canonicalFlavor": "large", + "minDiskAvailableGb": 1600.0, + "minMainMemoryAvailableGb": 32.0, + "description": "Flavor-name-is-large", + "minCpuCores": 4.0, + "fastDisk": true, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 1, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "allowedToBeDown": false, + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + } + ], + "ipAddresses": [ + "::1", + "127.0.0.1" + ], + "additionalIpAddresses": [ + "::2", + "::3", + "::4" + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json new file mode 100644 index 00000000000..f877d33672f --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node3.json @@ -0,0 +1,70 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost3.yahoo.com", + "id": "dockerhost3.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost3.yahoo.com", + "openStackId": "dockerhost3", + "flavor": "large", + "canonicalFlavor": "large", + "minDiskAvailableGb": 1600.0, + "minMainMemoryAvailableGb": 32.0, + "description": "Flavor-name-is-large", + "minCpuCores": 4.0, + "fastDisk": true, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 2, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "allowedToBeDown": false, + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + } + ], + "ipAddresses": [ + "::1", + "127.0.0.1" + ], + "additionalIpAddresses": [ + "::2", + "::3", + "::4" + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json new file mode 100644 index 00000000000..913cf9852aa --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node4.json @@ -0,0 +1,70 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost4.yahoo.com", + "id": "dockerhost4.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost4.yahoo.com", + "openStackId": "dockerhost4", + "flavor": "large", + "canonicalFlavor": "large", + "minDiskAvailableGb": 1600.0, + "minMainMemoryAvailableGb": 32.0, + "description": "Flavor-name-is-large", + "minCpuCores": 4.0, + "fastDisk": true, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 3, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "allowedToBeDown": false, + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + } + ], + "ipAddresses": [ + "::1", + "127.0.0.1" + ], + "additionalIpAddresses": [ + "::2", + "::3", + "::4" + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json new file mode 100644 index 00000000000..685b0a52b15 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/docker-node5.json @@ -0,0 +1,70 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/dockerhost5.yahoo.com", + "id": "dockerhost5.yahoo.com", + "state": "active", + "type": "host", + "hostname": "dockerhost5.yahoo.com", + "openStackId": "dockerhost5", + "flavor": "large", + "canonicalFlavor": "large", + "minDiskAvailableGb": 1600.0, + "minMainMemoryAvailableGb": 32.0, + "description": "Flavor-name-is-large", + "minCpuCores": 4.0, + "fastDisk": true, + "environment": "BARE_METAL", + "owner": { + "tenant": "zoneapp", + "application": "zoneapp", + "instance": "zoneapp" + }, + "membership": { + "clustertype": "container", + "clusterid": "node-admin", + "group": "0", + "index": 4, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion": "6.42.0", + "allowedToBeDown": false, + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + }, + { + "event": "reserved", + "at": 123, + "agent": "application" + }, + { + "event": "activated", + "at": 123, + "agent": "application" + } + ], + "ipAddresses": [ + "::1", + "127.0.0.1" + ], + "additionalIpAddresses": [ + "::2", + "::3", + "::4" + ] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json index fea4fb8d4d2..c09829e7f85 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json @@ -28,6 +28,9 @@ "name":"OperatorChangeApplicationMaintainer" }, { + "name":"ProvisionedExpirer" + }, + { "name":"RetiredEarlyExpirer" }, { @@ -43,4 +46,4 @@ "inactive":[ "NodeFailer" ] -}
\ No newline at end of file +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json index f311c240b1d..cb250e2033b 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node4-after-changes.json @@ -1,7 +1,7 @@ { "url": "http://localhost:8080/nodes/v2/node/host4.yahoo.com", "id": "host4.yahoo.com", - "state": "reserved", + "state": "failed", "type": "tenant", "hostname": "host4.yahoo.com", "parentHostname": "parent.yahoo.com", @@ -12,36 +12,36 @@ "minMainMemoryAvailableGb": 12.0, "description": "Flavor-name-is-medium-disk", "minCpuCores": 6.0, - "fastDisk":true, + "fastDisk": true, "environment": "BARE_METAL", "owner": { - "tenant": "tenant1", - "application": "application1", - "instance": "instance1" + "tenant": "tenant3", + "application": "application3", + "instance": "instance3" }, "membership": { - "clustertype": "container", - "clusterid": "id1", + "clustertype": "content", + "clusterid": "id3", "group": "0", - "index": 1, + "index": 0, "retired": false }, - "restartGeneration": 0, + "restartGeneration": 1, "currentRestartGeneration": 1, "wantedDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.42.0", "wantedVespaVersion": "6.42.0", "allowedToBeDown": false, - "rebootGeneration": 2, + "rebootGeneration": 3, "currentRebootGeneration": 1, "vespaVersion": "6.43.0", "currentDockerImage": "docker-registry.domain.tld:8080/dist/vespa:6.43.0", "hostedVersion": "6.43.0", "convergedStateVersion": "6.43.0", - "failCount": 0, + "failCount": 1, "hardwareFailure": true, "hardwareFailureDescription": "memory_mcelog", - "wantToRetire" : true, - "wantToDeprovision" : true, + "wantToRetire": true, + "wantToDeprovision": true, "history": [ { "event": "provisioned", @@ -51,7 +51,7 @@ { "event": "readied", "at": 123, - "agent": "system" + "agent": "system" }, { "event": "reserved", @@ -59,11 +59,24 @@ "agent": "application" }, { + "event": "activated", + "at": 123, + "agent": "application" + }, + { + "event": "failed", + "at": 123, + "agent": "operator" + }, + { "event": "rebooted", "at": 123, "agent": "system" } ], - "ipAddresses":["127.0.0.1", "::1"], - "additionalIpAddresses":[] + "ipAddresses": [ + "127.0.0.1", + "::1" + ], + "additionalIpAddresses": [] } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json new file mode 100644 index 00000000000..4560e5e1be4 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55-after-changes.json @@ -0,0 +1,39 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com", + "id": "host55.yahoo.com", + "state": "ready", + "type": "tenant", + "hostname": "host55.yahoo.com", + "openStackId": "node55", + "flavor": "default", + "canonicalFlavor": "default", + "minDiskAvailableGb": 400.0, + "minMainMemoryAvailableGb": 16.0, + "description": "Flavor-name-is-default", + "minCpuCores": 2.0, + "fastDisk": true, + "environment": "BARE_METAL", + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure": false, + "wantToRetire": false, + "wantToDeprovision": false, + "history": [ + { + "event": "provisioned", + "at": 123, + "agent": "system" + }, + { + "event": "readied", + "at": 123, + "agent": "system" + } + ], + "ipAddresses": [ + "::1", + "127.0.0.1" + ], + "additionalIpAddresses": [] +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json index 7d07037d076..4f8b988ccbf 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node55.json @@ -17,8 +17,8 @@ "currentRebootGeneration": 0, "failCount": 0, "hardwareFailure": false, - "wantToRetire": false, - "wantToDeprovision": false, + "wantToRetire": true, + "wantToDeprovision": true, "history": [ { "event": "provisioned", diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json index a9feed81674..475b914989b 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes-recursive.json @@ -1,14 +1,19 @@ { "nodes": [ @include(node7.json), - @include(parent1.json), + @include(node3.json), @include(node10.json), - @include(node4.json), + @include(node1.json), + @include(docker-node4.json), @include(node6.json), - @include(node3.json), + @include(docker-node5.json), + @include(docker-node2.json), @include(node2.json), - @include(node1.json), + @include(docker-node1.json), + @include(docker-node3.json), + @include(docker-container1.json), + @include(node4.json), @include(node55.json), @include(node5.json) ] -}
\ No newline at end of file +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json index 67b65259f8a..3bfaa95d5ee 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/nodes.json @@ -4,31 +4,46 @@ "url": "http://localhost:8080/nodes/v2/node/host7.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/parent1.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/host3.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host10.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/host10.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host4.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/host1.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host6.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/dockerhost4.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host3.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/host6.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host2.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/dockerhost5.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host1.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/dockerhost2.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host55.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/host2.yahoo.com" }, { - "url":"http://localhost:8080/nodes/v2/node/host5.yahoo.com" + "url": "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com" + }, + { + "url": "http://localhost:8080/nodes/v2/node/dockerhost3.yahoo.com" + }, + { + "url": "http://localhost:8080/nodes/v2/node/test-container-1" + }, + { + "url": "http://localhost:8080/nodes/v2/node/host4.yahoo.com" + }, + { + "url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com" + }, + { + "url": "http://localhost:8080/nodes/v2/node/host5.yahoo.com" } ] -}
\ No newline at end of file +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json deleted file mode 100644 index 81ca0465c4b..00000000000 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/parent-nodes.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "nodes": [ - @include(node10.json), - @include(node5.json) - ] -}
\ No newline at end of file diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json index 4ee1d5ed9b9..183f81ee3e1 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/states-recursive.json @@ -9,23 +9,28 @@ "ready": { "url": "http://localhost:8080/nodes/v2/state/ready", "nodes": [ - @include(parent1.json) + @include(node3.json) ] }, "reserved": { "url": "http://localhost:8080/nodes/v2/state/reserved", "nodes": [ @include(node10.json), - @include(node4.json) + @include(node1.json) ] }, "active": { "url": "http://localhost:8080/nodes/v2/state/active", "nodes": [ + @include(docker-node4.json), + @include(docker-node5.json), + @include(docker-node2.json), + @include(docker-node1.json), + @include(docker-node3.json), @include(node6.json), - @include(node3.json), @include(node2.json), - @include(node1.json) + @include(docker-container1.json), + @include(node4.json) ] }, "inactive": { diff --git a/orchestrator-restapi/pom.xml b/orchestrator-restapi/pom.xml index db823f3fb15..310512812e2 100644 --- a/orchestrator-restapi/pom.xml +++ b/orchestrator-restapi/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>orchestrator-restapi</artifactId> <version>6-SNAPSHOT</version> diff --git a/orchestrator/pom.xml b/orchestrator/pom.xml index c50f9ddcb29..ae22b6718f9 100644 --- a/orchestrator/pom.xml +++ b/orchestrator/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>orchestrator</artifactId> <version>6-SNAPSHOT</version> diff --git a/parent/pom.xml b/parent/pom.xml new file mode 100644 index 00000000000..0a19d66d674 --- /dev/null +++ b/parent/pom.xml @@ -0,0 +1,718 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>parent</artifactId> + <packaging>pom</packaging> + <version>6-SNAPSHOT</version> + <name>parent</name> + <description>Parent artifact for all Vespa maven projects.</description> + <url>https://github.com/vespa-engine</url> + + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>container-dependency-versions</artifactId> + <version>6-SNAPSHOT</version> + <relativePath>../container-dependency-versions/pom.xml</relativePath> + </parent> + + <licenses> + <license> + <name>The Apache License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + </license> + </licenses> + <developers> + <developer> + <name>Vespa</name> + <url>https://github.com/vespa-engine</url> + </developer> + </developers> + <distributionManagement> + <repository> + <id>bintray-vespa-repo</id> + <url>https://api.bintray.com/maven/yahoo/maven/vespa;publish=1</url> + </repository> + </distributionManagement> + <scm> + <connection>scm:git:git@github.com:vespa-engine/vespa.git</connection> + <developerConnection>scm:git:git@github.com:vespa-engine/vespa.git</developerConnection> + <url>git@github.com:vespa-engine/vespa.git</url> + </scm> + + <repositories> + <!-- Required for Athenz libraries --> + <repository> + <snapshots> + <enabled>false</enabled> + </snapshots> + <id>bintray-yahoo-maven</id> + <name>bintray</name> + <url>https://yahoo.bintray.com/maven</url> + </repository> + </repositories> + + <build> + <finalName>${project.artifactId}</finalName> + <extensions> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh-external</artifactId> + <version>2.7</version> + </extension> + <extension> + <groupId>org.apache.maven.archetype</groupId> + <artifactId>archetype-packaging</artifactId> + <version>2.0</version> + </extension> + </extensions> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.antlr</groupId> + <artifactId>antlr3-maven-plugin</artifactId> + <version>${antlr.version}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <version>1.7</version> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>2.4.0</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <version>2.4</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.6.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + <showWarnings>true</showWarnings> + <optimize>true</optimize> + <showDeprecation>false</showDeprecation> + <compilerArgs> + <arg>-Xlint:all</arg> + <arg>-Xlint:-serial</arg> + <arg>-Xlint:-try</arg> + <arg>-Xlint:-processing</arg> + <arg>-Xlint:-varargs</arg> + <arg>-Werror</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <version>2.10</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>2.5</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + <version>2.5.2</version> + <configuration> + <updateReleaseInfo>true</updateReleaseInfo> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.0.2</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam> + </configuration> + <version>2.10.4</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-plugin-plugin</artifactId> + <version>3.5</version> + <configuration> + <!-- see http://jira.codehaus.org/browse/MNG-5346 --> + <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound> + </configuration> + <executions> + <execution> + <id>mojo-descriptor</id> + <goals> + <goal>descriptor</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>2.7</version> + <configuration> + <escapeString>\</escapeString> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-site-plugin</artifactId> + <version>3.3</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>2.1.2</version> + <configuration> + <includePom>true</includePom> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <redirectTestOutputToFile>${test.hide}</redirectTestOutputToFile> + <systemPropertyVariables> + <java.io.tmpdir>${project.build.directory}</java.io.tmpdir> + </systemPropertyVariables> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-report-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport> + <showSuccess>false</showSuccess> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>1.9.1</version> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.6.0</version> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>javacc-maven-plugin</artifactId> + <version>2.6</version> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>properties-maven-plugin</artifactId> + <version>1.0.0</version> + </plugin> + <plugin> + <groupId>net.alchim31.maven</groupId> + <artifactId>scala-maven-plugin</artifactId> + <version>3.2.2</version> + <configuration> + <args> + <arg>-unchecked</arg> + <arg>-deprecation</arg> + <arg>-feature</arg> + <arg>-Xfatal-warnings</arg> + </args> + </configuration> + </plugin> + <plugin> + <groupId>com.yahoo.vespa</groupId> + <artifactId>bundle-plugin</artifactId> + <version>${project.version}</version> + <configuration> + <configGenVersion>${project.version}</configGenVersion> + <useCommonAssemblyIds>true</useCommonAssemblyIds> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + <profiles> + <profile> + <id>attach-sources</id> + <activation> + <property> + <name>!skipSources</name> + </property> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>generate-javadoc</id> + <activation> + <property> + <name>!skipJavadoc</name> + </property> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <executions> + <execution> + <id>generate-javadoc</id> + <phase>package</phase> + <goals> + <goal>javadoc</goal> + </goals> + </execution> + </executions> + <configuration> + <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam> + <failOnError>${javadoc.failOnError}</failOnError> + <quiet>true</quiet> + <show>private</show> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>coverage</id> + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <configuration> + <includePluginDependencies>true</includePluginDependencies> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>src/main/scala</source> + </sources> + </configuration> + </execution> + <execution> + <id>add-test-source</id> + <phase>generate-test-sources</phase> + <goals> + <goal>add-test-source</goal> + </goals> + <configuration> + <sources> + <source>src/test/scala</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>sign-artifacts</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh-external</artifactId> + <version>2.7</version> + </dependency> + <dependency> + <groupId>com.github.cverges.expect4j</groupId> + <artifactId>expect4j</artifactId> + <version>1.6</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + <version>1.11</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-exec</artifactId> + <version>1.3</version> + </dependency> + <dependency> + <groupId>io.airlift</groupId> + <artifactId>airline</artifactId> + <version>0.7</version> + </dependency> + <dependency> + <groupId>org.ow2.asm</groupId> + <artifactId>asm</artifactId> + <version>5.2</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava-testlib</artifactId> + <version>18.0</version> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>2.4.1</version> + </dependency> + <dependency> + <groupId>com.googlecode.jmockit</groupId> + <artifactId>jmockit</artifactId> + <version>1.2</version> + </dependency> + <dependency> + <groupId>com.goldmansachs</groupId> + <artifactId>gs-collections</artifactId> + <version>6.1.0</version> + </dependency> + <dependency> + <groupId>com.infradna.tool</groupId> + <artifactId>bridge-method-annotation</artifactId> + <version>1.4</version> + </dependency> + <dependency> + <groupId>commons-cli</groupId> + <artifactId>commons-cli</artifactId> + <version>1.3.1</version> + </dependency> + <dependency> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + <version>3.2.1</version> + </dependency> + <dependency> + <groupId>commons-configuration</groupId> + <artifactId>commons-configuration</artifactId> + <version>1.6</version> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.4</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${commons-lang.version}</version> + </dependency> + <dependency> + <groupId>commons-net</groupId> + <artifactId>commons-net</artifactId> + <version>2.0</version> + </dependency> + <dependency> + <groupId>commons-pool</groupId> + <artifactId>commons-pool</artifactId> + <version>1.5.6</version> + </dependency> + <!-- Explicitly included to get Zookeeper version 3.4.10, + can be excluded if you want the Zookeeper version + used by curator by default + --> + <dependency> + <groupId>org.apache.zookeeper</groupId> + <artifactId>zookeeper</artifactId> + <version>3.4.10</version> + </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-recipes</artifactId> + <version>${curator.version}</version> + </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-test</artifactId> + <version>${curator.version}</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + </dependency> + <dependency> + <groupId>org.antlr</groupId> + <artifactId>antlr-runtime</artifactId> + <version>${antlr.version}</version> + </dependency> + <dependency> + <groupId>org.antlr</groupId> + <artifactId>antlr4-runtime</artifactId> + <version>${antlr4.version}</version> + </dependency> + <dependency> + <groupId>org.apache.aries.spifly</groupId> + <artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId> + <version>${aries.spifly.version}</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.1</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>fluent-hc</artifactId> + <version>4.3.6</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpmime</artifactId> + <version>4.3.6</version> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-artifact</artifactId> + <version>3.5.0</version> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-core</artifactId> + <version>3.5.0</version> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-model</artifactId> + <version>3.5.0</version> + </dependency> + <dependency> + <groupId>org.apache.maven.plugin-tools</groupId> + <artifactId>maven-plugin-annotations</artifactId> + <version>3.5</version> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-plugin-api</artifactId> + <version>3.5.0</version> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-project</artifactId> + <version>2.2.1</version> + </dependency> + <dependency> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.0.2</version> + </dependency> + <dependency> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>surefire-junit4</artifactId> + <version>${surefire.version}</version> + </dependency> + <dependency> + <groupId>org.apache.maven.surefire</groupId> + <artifactId>surefire-providers</artifactId> + <version>${surefire.version}</version> + <type>pom</type> + </dependency> + <dependency> + <groupId>org.codehaus.jettison</groupId> + <artifactId>jettison</artifactId> + <version>1.3.1</version> + </dependency> + <dependency> + <groupId>org.cthul</groupId> + <artifactId>cthul-matchers</artifactId> + <version>1.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-all</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-core</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>uk.co.datumedge</groupId> + <artifactId>hamcrest-json</artifactId> + <version>0.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hdrhistogram</groupId> + <artifactId>HdrHistogram</artifactId> + <version>2.1.8</version> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.9.5</version> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + <version>4.3.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <version>4.3.0</version> + </dependency> + <dependency> + <groupId>org.scala-lang</groupId> + <artifactId>scala-library</artifactId> + <version>${scala.version}</version> + </dependency> + <dependency> + <groupId>org.scala-lang.modules</groupId> + <artifactId>scala-parser-combinators_${scala.major-version}</artifactId> + <version>1.0.1</version> + </dependency> + <dependency> + <groupId>org.scala-lang.modules</groupId> + <artifactId>scala-xml_${scala.major-version}</artifactId> + <version>1.0.2</version> + </dependency> + <dependency> + <groupId>org.scalatest</groupId> + <artifactId>scalatest_${scala.major-version}</artifactId> + <version>2.2.2</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <version>4.0.6.RELEASE</version> + </dependency> + <dependency> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + <version>6.10</version> + </dependency> + <dependency> + <groupId>org.twdata.maven</groupId> + <artifactId>mojo-executor</artifactId> + <version>2.3.0</version> + </dependency> + <dependency> + <groupId>net.spy</groupId> + <artifactId>spymemcached</artifactId> + <version>2.10.1</version> + </dependency> + <dependency> + <groupId>xerces</groupId> + <artifactId>xercesImpl</artifactId> + <version>2.11.0</version> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + <version>${bouncycastle.version}</version> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>${bouncycastle.version}</version> + </dependency> + <dependency> + <groupId>com.ibm.icu</groupId> + <artifactId>icu4j</artifactId> + <version>57.1</version> + </dependency> + <dependency> + <groupId>com.yahoo.athenz</groupId> + <artifactId>athenz-zms-java-client</artifactId> + <version>${athenz.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.athenz</groupId> + <artifactId>athenz-zts-java-client</artifactId> + <version>${athenz.version}</version> + </dependency> + </dependencies> + </dependencyManagement> + + <properties> + <antlr.version>3.5.2</antlr.version> + <antlr4.version>4.5</antlr4.version> + <aries.spifly.version>1.0.8</aries.spifly.version> + <aries.util.version>1.0.0</aries.util.version> + <asm-debug-all.version>5.0.3</asm-debug-all.version> + <!-- Athenz dependencies. Make sure these dependencies matches those in Vespa's internal repositories --> + <athenz.version>1.7.28</athenz.version> + <bouncycastle.version>1.58</bouncycastle.version> + <commons-lang.version>2.6</commons-lang.version> + <!-- WARNING: If you change curator version, you also need to update + zkfacade/src/main/java/org/apache/curator/**/package-info.java + using something like + find zkfacade/src/main/java/org/apache/curator -name package-info.java | \ + xargs perl -pi -e 's/major = [0-9]+, minor = [0-9]+, micro = [0-9]+/major = 2, minor = 9, micro = 1/g' + --> + <curator.version>2.9.1</curator.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <test.hide>true</test.hide> + <doclint>all</doclint> + <scala.major-version>2.11</scala.major-version> + <scala.version>${scala.major-version}.4</scala.version> + <surefire.version>2.19.1</surefire.version> <!-- NOTE bjorncs 15.06.2017: Version 2.20 has OoM issues --> + </properties> + +</project> @@ -3,11 +3,10 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yahoo.vespa</groupId> - <artifactId>parent</artifactId> + <artifactId>vespa</artifactId> <packaging>pom</packaging> <version>6-SNAPSHOT</version> - <name>parent</name> - <description>Parent artifact for all Vespa maven projects.</description> + <description>Aggregator pom for vespa.</description> <url>https://github.com/vespa-engine</url> <licenses> @@ -24,941 +23,6 @@ </developer> </developers> - <distributionManagement> - <repository> - <id>bintray-vespa-repo</id> - <url>https://api.bintray.com/maven/yahoo/maven/vespa;publish=1</url> - </repository> - </distributionManagement> - - <repositories> - <!-- Required for Athenz libraries --> - <repository> - <snapshots> - <enabled>false</enabled> - </snapshots> - <id>bintray-yahoo-maven</id> - <name>bintray</name> - <url>https://yahoo.bintray.com/maven</url> - </repository> - </repositories> - - <scm> - <connection>scm:git:git@github.com:vespa-engine/vespa.git</connection> - <developerConnection>scm:git:git@github.com:vespa-engine/vespa.git</developerConnection> - <url>git@github.com:vespa-engine/vespa.git</url> - </scm> - - <build> - <finalName>${project.artifactId}</finalName> - <extensions> - <extension> - <groupId>org.apache.maven.wagon</groupId> - <artifactId>wagon-ssh-external</artifactId> - <version>2.7</version> - </extension> - <extension> - <groupId>org.apache.maven.archetype</groupId> - <artifactId>archetype-packaging</artifactId> - <version>2.0</version> - </extension> - </extensions> - <pluginManagement> - <plugins> - <plugin> - <groupId>org.antlr</groupId> - <artifactId>antlr3-maven-plugin</artifactId> - <version>${antlr.version}</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-antrun-plugin</artifactId> - <version>1.7</version> - </plugin> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>maven-bundle-plugin</artifactId> - <version>2.4.0</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-assembly-plugin</artifactId> - <version>2.4</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <version>3.6.1</version> - <configuration> - <source>1.8</source> - <target>1.8</target> - <showWarnings>true</showWarnings> - <optimize>true</optimize> - <showDeprecation>false</showDeprecation> - <compilerArgs> - <arg>-Xlint:all</arg> - <arg>-Xlint:-serial</arg> - <arg>-Xlint:-try</arg> - <arg>-Xlint:-processing</arg> - <arg>-Xlint:-varargs</arg> - <arg>-Werror</arg> - </compilerArgs> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-dependency-plugin</artifactId> - <version>2.10</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-deploy-plugin</artifactId> - <version>2.5</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-install-plugin</artifactId> - <version>2.5.2</version> - <configuration> - <updateReleaseInfo>true</updateReleaseInfo> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> - <configuration> - <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam> - </configuration> - <version>2.10.4</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-plugin-plugin</artifactId> - <version>3.5</version> - <configuration> - <!-- see http://jira.codehaus.org/browse/MNG-5346 --> - <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound> - </configuration> - <executions> - <execution> - <id>mojo-descriptor</id> - <goals> - <goal>descriptor</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-resources-plugin</artifactId> - <version>2.7</version> - <configuration> - <escapeString>\</escapeString> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-site-plugin</artifactId> - <version>3.3</version> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <version>2.1.2</version> - <configuration> - <includePom>true</includePom> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire.version}</version> - <configuration> - <redirectTestOutputToFile>${test.hide}</redirectTestOutputToFile> - <systemPropertyVariables> - <java.io.tmpdir>${project.build.directory}</java.io.tmpdir> - </systemPropertyVariables> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-report-plugin</artifactId> - <version>${surefire.version}</version> - <configuration> - <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport> - <showSuccess>false</showSuccess> - </configuration> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>build-helper-maven-plugin</artifactId> - <version>1.9.1</version> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>1.6.0</version> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>javacc-maven-plugin</artifactId> - <version>2.6</version> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>properties-maven-plugin</artifactId> - <version>1.0.0</version> - </plugin> - <plugin> - <groupId>net.alchim31.maven</groupId> - <artifactId>scala-maven-plugin</artifactId> - <version>3.2.2</version> - <configuration> - <args> - <arg>-unchecked</arg> - <arg>-deprecation</arg> - <arg>-feature</arg> - <arg>-Xfatal-warnings</arg> - </args> - </configuration> - </plugin> - <plugin> - <groupId>com.yahoo.vespa</groupId> - <artifactId>bundle-plugin</artifactId> - <version>${project.version}</version> - <configuration> - <configGenVersion>${project.version}</configGenVersion> - <useCommonAssemblyIds>true</useCommonAssemblyIds> - </configuration> - </plugin> - </plugins> - </pluginManagement> - </build> - <profiles> - <profile> - <id>attach-sources</id> - <activation> - <property> - <name>!skipSources</name> - </property> - </activation> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-source-plugin</artifactId> - <executions> - <execution> - <id>attach-sources</id> - <goals> - <goal>jar-no-fork</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> - <profile> - <id>generate-javadoc</id> - <activation> - <property> - <name>!skipJavadoc</name> - </property> - </activation> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> - <executions> - <execution> - <id>generate-javadoc</id> - <phase>package</phase> - <goals> - <goal>javadoc</goal> - </goals> - </execution> - </executions> - <configuration> - <additionalparam>-Xdoclint:${doclint} -Xdoclint:-missing</additionalparam> - <failOnError>${javadoc.failOnError}</failOnError> - <quiet>true</quiet> - <show>private</show> - </configuration> - </plugin> - </plugins> - </build> - </profile> - <profile> - <id>coverage</id> - <build> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <configuration> - <includePluginDependencies>true</includePluginDependencies> - </configuration> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>build-helper-maven-plugin</artifactId> - <executions> - <execution> - <phase>generate-sources</phase> - <goals> - <goal>add-source</goal> - </goals> - <configuration> - <sources> - <source>src/main/scala</source> - </sources> - </configuration> - </execution> - <execution> - <id>add-test-source</id> - <phase>generate-test-sources</phase> - <goals> - <goal>add-test-source</goal> - </goals> - <configuration> - <sources> - <source>src/test/scala</source> - </sources> - </configuration> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> - <profile> - <id>sign-artifacts</id> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-gpg-plugin</artifactId> - <version>1.6</version> - <executions> - <execution> - <id>sign-artifacts</id> - <phase>verify</phase> - <goals> - <goal>sign</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - </profile> - </profiles> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.apache.maven.wagon</groupId> - <artifactId>wagon-ssh-external</artifactId> - <version>2.7</version> - </dependency> - <dependency> - <groupId>com.github.cverges.expect4j</groupId> - <artifactId>expect4j</artifactId> - <version>1.6</version> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-compress</artifactId> - <version>1.11</version> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-exec</artifactId> - <version>1.3</version> - </dependency> - <dependency> - <groupId>io.airlift</groupId> - <artifactId>airline</artifactId> - <version>0.7</version> - </dependency> - <dependency> - <groupId>aopalliance</groupId> - <artifactId>aopalliance</artifactId> - <version>1.0</version> - </dependency> - <dependency> - <groupId>org.ow2.asm</groupId> - <artifactId>asm</artifactId> - <version>5.2</version> - </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>annotations</artifactId> - <version>1.3.9</version> - </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>jsr305</artifactId> - <version>1.3.9</version> - </dependency> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <version>18.0</version> - </dependency> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava-testlib</artifactId> - <version>18.0</version> - </dependency> - <dependency> - <groupId>com.google.inject</groupId> - <artifactId>guice</artifactId> - <version>3.0</version> - </dependency> - <dependency> - <groupId>com.google.inject</groupId> - <artifactId>guice</artifactId> - <version>3.0</version> - <classifier>no_aop</classifier> - </dependency> - <dependency> - <groupId>com.google.inject.extensions</groupId> - <artifactId>guice-assistedinject</artifactId> - <version>3.0</version> - </dependency> - <dependency> - <groupId>com.google.inject.extensions</groupId> - <artifactId>guice-multibindings</artifactId> - <version>3.0</version> - </dependency> - <dependency> - <groupId>com.google.protobuf</groupId> - <artifactId>protobuf-java</artifactId> - <version>2.4.1</version> - </dependency> - <dependency> - <groupId>com.googlecode.jmockit</groupId> - <artifactId>jmockit</artifactId> - <version>1.2</version> - </dependency> - <dependency> - <groupId>com.goldmansachs</groupId> - <artifactId>gs-collections</artifactId> - <version>6.1.0</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-core</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-annotations</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.jaxrs</groupId> - <artifactId>jackson-jaxrs-json-provider</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.module</groupId> - <artifactId>jackson-module-jaxb-annotations</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.jaxrs</groupId> - <artifactId>jackson-jaxrs-base</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.jaxrs</groupId> - <artifactId>jackson-jaxrs-xml-provider</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.dataformat</groupId> - <artifactId>jackson-dataformat-xml</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.datatype</groupId> - <artifactId>jackson-datatype-jdk8</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.datatype</groupId> - <artifactId>jackson-datatype-jsr310</artifactId> - <version>${jackson2.version}</version> - </dependency> - <dependency> - <groupId>com.infradna.tool</groupId> - <artifactId>bridge-method-annotation</artifactId> - <version>1.4</version> - </dependency> - <dependency> - <groupId>commons-cli</groupId> - <artifactId>commons-cli</artifactId> - <version>1.3.1</version> - </dependency> - <dependency> - <groupId>commons-codec</groupId> - <artifactId>commons-codec</artifactId> - <version>1.4</version> - </dependency> - <dependency> - <groupId>commons-collections</groupId> - <artifactId>commons-collections</artifactId> - <version>3.2.1</version> - </dependency> - <dependency> - <groupId>commons-configuration</groupId> - <artifactId>commons-configuration</artifactId> - <version>1.6</version> - </dependency> - <dependency> - <groupId>commons-daemon</groupId> - <artifactId>commons-daemon</artifactId> - <version>1.0.3</version> - </dependency> - <dependency> - <groupId>commons-io</groupId> - <artifactId>commons-io</artifactId> - <version>2.4</version> - </dependency> - <dependency> - <groupId>commons-lang</groupId> - <artifactId>commons-lang</artifactId> - <version>${commons-lang.version}</version> - </dependency> - <dependency> - <!-- This version is exported by jdisc via jcl-over-slf4j. --> - <groupId>commons-logging</groupId> - <artifactId>commons-logging</artifactId> - <version>1.1.1</version> - </dependency> - <dependency> - <groupId>commons-net</groupId> - <artifactId>commons-net</artifactId> - <version>2.0</version> - </dependency> - <dependency> - <groupId>commons-pool</groupId> - <artifactId>commons-pool</artifactId> - <version>1.5.6</version> - </dependency> - <!-- Explicitly included to get Zookeeper version 3.4.10, - can be excluded if you want the Zookeeper version - used by curator by default - --> - <dependency> - <groupId>org.apache.zookeeper</groupId> - <artifactId>zookeeper</artifactId> - <version>3.4.10</version> - </dependency> - <dependency> - <groupId>org.apache.curator</groupId> - <artifactId>curator-recipes</artifactId> - <version>${curator.version}</version> - </dependency> - <dependency> - <groupId>org.apache.curator</groupId> - <artifactId>curator-test</artifactId> - <version>${curator.version}</version> - </dependency> - <dependency> - <groupId>javax.servlet</groupId> - <artifactId>javax.servlet-api</artifactId> - <version>3.1.0</version> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.12</version> - </dependency> - <dependency> - <groupId>org.antlr</groupId> - <artifactId>antlr-runtime</artifactId> - <version>${antlr.version}</version> - </dependency> - <dependency> - <groupId>org.antlr</groupId> - <artifactId>antlr4-runtime</artifactId> - <version>${antlr4.version}</version> - </dependency> - <dependency> - <groupId>org.apache.aries.spifly</groupId> - <artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId> - <version>${aries.spifly.version}</version> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <version>3.1</version> - </dependency> - <dependency> - <groupId>org.apache.felix</groupId> - <artifactId>org.apache.felix.framework</artifactId> - <version>4.2.1</version> - </dependency> - <dependency> - <groupId>org.apache.felix</groupId> - <artifactId>org.apache.felix.log</artifactId> - <version>1.0.1</version> - </dependency> - <dependency> - <groupId>org.apache.felix</groupId> - <artifactId>org.apache.felix.main</artifactId> - <version>4.2.1</version> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>fluent-hc</artifactId> - <version>4.3.6</version> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpclient</artifactId> - <version>4.3.6</version> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpcore</artifactId> - <version>4.3.3</version> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents</groupId> - <artifactId>httpmime</artifactId> - <version>4.3.6</version> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-artifact</artifactId> - <version>3.5.0</version> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-core</artifactId> - <version>3.5.0</version> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-model</artifactId> - <version>3.5.0</version> - </dependency> - <dependency> - <groupId>org.apache.maven.plugin-tools</groupId> - <artifactId>maven-plugin-annotations</artifactId> - <version>3.5</version> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-plugin-api</artifactId> - <version>3.5.0</version> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-project</artifactId> - <version>2.2.1</version> - </dependency> - <dependency> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> - </dependency> - <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-junit4</artifactId> - <version>${surefire.version}</version> - </dependency> - <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-providers</artifactId> - <version>${surefire.version}</version> - <type>pom</type> - </dependency> - <dependency> - <groupId>org.codehaus.jettison</groupId> - <artifactId>jettison</artifactId> - <version>1.3.1</version> - </dependency> - <dependency> - <groupId>org.cthul</groupId> - <artifactId>cthul-matchers</artifactId> - <version>1.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-continuation</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-server</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlet</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlets</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-util</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-http</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-jmx</artifactId> - <version>${jetty.version}</version> - </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-all</artifactId> - <version>1.3</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-core</artifactId> - <version>1.3</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-library</artifactId> - <version>1.3</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>uk.co.datumedge</groupId> - <artifactId>hamcrest-json</artifactId> - <version>0.2</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.hdrhistogram</groupId> - <artifactId>HdrHistogram</artifactId> - <version>2.1.8</version> - </dependency> - <dependency> - <groupId>org.json</groupId> - <artifactId>json</artifactId> - <version>20090211</version> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-all</artifactId> - <version>1.9.5</version> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <version>1.9.5</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>org.osgi.compendium</artifactId> - <version>4.3.0</version> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>org.osgi.core</artifactId> - <version>4.3.0</version> - </dependency> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library</artifactId> - <version>${scala.version}</version> - </dependency> - <dependency> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-parser-combinators_${scala.major-version}</artifactId> - <version>1.0.1</version> - </dependency> - <dependency> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-xml_${scala.major-version}</artifactId> - <version>1.0.2</version> - </dependency> - <dependency> - <groupId>org.scalatest</groupId> - <artifactId>scalatest_${scala.major-version}</artifactId> - <version>2.2.2</version> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>jcl-over-slf4j</artifactId> - <version>1.7.5</version> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>log4j-over-slf4j</artifactId> - <version>1.7.5</version> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - <version>1.7.5</version> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-jdk14</artifactId> - <version>1.7.5</version> - </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-test</artifactId> - <version>4.0.6.RELEASE</version> - </dependency> - <dependency> - <groupId>org.testng</groupId> - <artifactId>testng</artifactId> - <version>6.10</version> - </dependency> - <dependency> - <groupId>org.twdata.maven</groupId> - <artifactId>mojo-executor</artifactId> - <version>2.3.0</version> - </dependency> - <dependency> - <groupId>net.jcip</groupId> - <artifactId>jcip-annotations</artifactId> - <version>1.0</version> - </dependency> - <dependency> - <groupId>net.jpountz.lz4</groupId> - <artifactId>lz4</artifactId> - <version>1.3.0</version> - </dependency> - <dependency> - <groupId>net.spy</groupId> - <artifactId>spymemcached</artifactId> - <version>2.10.1</version> - </dependency> - <dependency> - <groupId>xerces</groupId> - <artifactId>xercesImpl</artifactId> - <version>2.11.0</version> - </dependency> - <dependency> - <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk15on</artifactId> - <version>${bouncycastle.version}</version> - </dependency> - <dependency> - <groupId>org.bouncycastle</groupId> - <artifactId>bcprov-jdk15on</artifactId> - <version>${bouncycastle.version}</version> - </dependency> - <!-- jersey 2 support --> - <dependency> - <groupId>javax.ws.rs</groupId> - <artifactId>javax.ws.rs-api</artifactId> - <version>${javax.ws.rs-api.version}</version> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.containers</groupId> - <artifactId>jersey-container-servlet-core</artifactId> - <version>${jersey2.version}</version> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.containers</groupId> - <artifactId>jersey-container-servlet</artifactId> - <version>${jersey2.version}</version> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.media</groupId> - <artifactId>jersey-media-json-jackson</artifactId> - <version>${jersey2.version}</version> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.media</groupId> - <artifactId>jersey-media-multipart</artifactId> - <version>${jersey2.version}</version> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.ext</groupId> - <artifactId>jersey-proxy-client</artifactId> - <version>${jersey2.version}</version> - </dependency> - <dependency> - <groupId>org.glassfish.jersey.core</groupId> - <artifactId>jersey-client</artifactId> - <version>${jersey2.version}</version> - </dependency> - <dependency> - <groupId>com.ibm.icu</groupId> - <artifactId>icu4j</artifactId> - <version>57.1</version> - </dependency> - <dependency> - <groupId>com.yahoo.athenz</groupId> - <artifactId>athenz-zms-java-client</artifactId> - <version>${athenz.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.athenz</groupId> - <artifactId>athenz-zts-java-client</artifactId> - <version>${athenz.version}</version> - </dependency> - </dependencies> - </dependencyManagement> - - <properties> - <javax.ws.rs-api.version>2.0.1</javax.ws.rs-api.version> <!-- must be kept in sync with version used by current jersey2.version --> - <antlr.version>3.5.2</antlr.version> - <antlr4.version>4.5</antlr4.version> - <aries.spifly.version>1.0.8</aries.spifly.version> - <aries.util.version>1.0.0</aries.util.version> - <asm-debug-all.version>5.0.3</asm-debug-all.version> - <!-- Athenz dependencies. Make sure these dependencies matches those in Vespa's internal repositories --> - <athenz.version>1.7.28</athenz.version> - <bouncycastle.version>1.58</bouncycastle.version> - <commons-lang.version>2.6</commons-lang.version> - <!-- WARNING: If you change curator version, you also need to update - zkfacade/src/main/java/org/apache/curator/**/package-info.java - using something like - find zkfacade/src/main/java/org/apache/curator -name package-info.java | \ - xargs perl -pi -e 's/major = [0-9]+, minor = [0-9]+, micro = [0-9]+/major = 2, minor = 9, micro = 1/g' - --> - <curator.version>2.9.1</curator.version> - <jackson2.version>2.8.3</jackson2.version> - <jersey2.version>2.23.2</jersey2.version> - <jetty.version>9.4.6.v20170531</jetty.version> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <test.hide>true</test.hide> - <doclint>all</doclint> - <scala.major-version>2.11</scala.major-version> - <scala.version>${scala.major-version}.4</scala.version> - <surefire.version>2.19.1</surefire.version> <!-- NOTE bjorncs 15.06.2017: Version 2.20 has OoM issues --> - </properties> - <modules> <module>application</module> <module>application-deploy-plugin</module> @@ -989,6 +53,7 @@ <module>container-core</module> <module>container-accesslogging</module> <module>container-dependencies-enforcer</module> + <module>container-dependency-versions</module> <module>container-dev</module> <module>container-di</module> <module>container-disc</module> @@ -1033,6 +98,7 @@ <module>node-maintainer</module> <module>orchestrator-restapi</module> <module>orchestrator</module> + <module>parent</module> <module>predicate-search</module> <module>predicate-search-core</module> <module>processing</module> diff --git a/predicate-search-core/pom.xml b/predicate-search-core/pom.xml index ea2995c4770..8ced58b840e 100644 --- a/predicate-search-core/pom.xml +++ b/predicate-search-core/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>predicate-search-core</artifactId> <version>6-SNAPSHOT</version> diff --git a/predicate-search/pom.xml b/predicate-search/pom.xml index ca79544c766..289250012a0 100644 --- a/predicate-search/pom.xml +++ b/predicate-search/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>predicate-search</artifactId> <version>6-SNAPSHOT</version> diff --git a/processing/pom.xml b/processing/pom.xml index 15aa984cbd2..9bb9ee77037 100644 --- a/processing/pom.xml +++ b/processing/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>processing</artifactId> <packaging>jar</packaging> diff --git a/provided-dependencies/pom.xml b/provided-dependencies/pom.xml index 18cdd294b87..33f1977c7e2 100755 --- a/provided-dependencies/pom.xml +++ b/provided-dependencies/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>provided-dependencies</artifactId> <packaging>jar</packaging> diff --git a/searchcore/pom.xml b/searchcore/pom.xml index f31617cfe53..3b43bf1205e 100644 --- a/searchcore/pom.xml +++ b/searchcore/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>searchcore</artifactId> <version>6-SNAPSHOT</version> diff --git a/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp b/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp index b8a3cceb153..6d69d9b225b 100644 --- a/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp +++ b/searchcore/src/tests/proton/attribute/exclusive_attribute_read_accessor/exclusive_attribute_read_accessor_test.cpp @@ -5,7 +5,7 @@ #include <vespa/searchcommon/attribute/config.h> #include <vespa/searchlib/attribute/attributefactory.h> #include <vespa/searchlib/common/sequencedtaskexecutor.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> using namespace proton; using namespace search; diff --git a/searchcore/src/tests/proton/common/selectpruner_test.cpp b/searchcore/src/tests/proton/common/selectpruner_test.cpp index cea72a556cc..4ec5bb928f6 100644 --- a/searchcore/src/tests/proton/common/selectpruner_test.cpp +++ b/searchcore/src/tests/proton/common/selectpruner_test.cpp @@ -618,8 +618,6 @@ TEST_F("Test that multiplication has higher priority than addition", TEST_F("Test that toplevel functions are visited", TestFixture) { - f.testPrune("searchcolumn.15 == 4", - "searchcolumn.15 == 4"); f.testPrune("id.scheme == \"doc\"", "id.scheme == \"doc\""); f.testPrune("test.aa < now() - 7200", diff --git a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp index 87efd74659d..d3567da3527 100644 --- a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp +++ b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp @@ -666,6 +666,42 @@ TEST("require that maintenance controller should change if some config has chang TEST_DO(assertMaintenanceControllerShouldChange(CCR().setMaintenanceChanged(true))); } +void +assertSubDbsShouldNotChange(DocumentDBConfig::ComparisonResult result) +{ + ReconfigParams params(result); + EXPECT_FALSE(params.configHasChanged()); + EXPECT_FALSE(params.shouldSubDbsChange()); +} + +void +assertSubDbsShouldChange(DocumentDBConfig::ComparisonResult result) +{ + ReconfigParams params(result); + EXPECT_TRUE(params.configHasChanged()); + EXPECT_TRUE(params.shouldSubDbsChange()); +} + + +TEST("require that subdbs should change if relevant config changed") +{ + TEST_DO(assertSubDbsShouldNotChange(CCR())); + EXPECT_FALSE(ReconfigParams(CCR().setMaintenanceChanged(true)).shouldSubDbsChange()); + TEST_DO(assertSubDbsShouldChange(CCR().setFlushChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setStoreChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setDocumenttypesChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setDocumentTypeRepoChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setSummaryChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setSummarymapChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setJuniperrcChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setAttributesChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setImportedFieldsChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setVisibilityDelayChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setRankProfilesChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setRankingConstantsChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setSchemaChanged(true))); +} + TEST_MAIN() { TEST_RUN_ALL(); diff --git a/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp b/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp index 0a4b83350ba..c1626e94809 100644 --- a/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp +++ b/searchcore/src/tests/proton/documentdb/job_tracked_maintenance_job/job_tracked_maintenance_job_test.cpp @@ -7,7 +7,7 @@ LOG_SETUP("job_tracked_maintenance_test"); #include <vespa/searchcore/proton/test/simple_job_tracker.h> #include <vespa/vespalib/testkit/testapp.h> #include <vespa/vespalib/util/closuretask.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/vespalib/util/threadstackexecutor.h> using namespace proton; diff --git a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp index b84aa1c1c6c..bf88a0c3003 100644 --- a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp +++ b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp @@ -32,8 +32,9 @@ #include <vespa/vespalib/data/slime/slime.h> #include <vespa/vespalib/testkit/testapp.h> #include <vespa/vespalib/util/closuretask.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <vespa/vespalib/util/threadstackexecutor.h> +#include <unistd.h> #include <vespa/log/log.h> LOG_SETUP("maintenancecontroller_test"); @@ -497,7 +498,8 @@ public: _mcCfg->getLidSpaceCompactionConfig(), _mcCfg->getAttributeUsageFilterConfig(), _mcCfg->getAttributeUsageSampleInterval(), - _mcCfg->getBlockableJobConfig())); + _mcCfg->getBlockableJobConfig(), + _mcCfg->getFlushConfig())); _mcCfg = newCfg; forwardMaintenanceConfig(); } @@ -514,7 +516,8 @@ public: _mcCfg->getLidSpaceCompactionConfig(), _mcCfg->getAttributeUsageFilterConfig(), _mcCfg->getAttributeUsageSampleInterval(), - _mcCfg->getBlockableJobConfig())); + _mcCfg->getBlockableJobConfig(), + _mcCfg->getFlushConfig())); _mcCfg = newCfg; forwardMaintenanceConfig(); } @@ -531,7 +534,8 @@ public: _mcCfg->getLidSpaceCompactionConfig(), _mcCfg->getAttributeUsageFilterConfig(), _mcCfg->getAttributeUsageSampleInterval(), - _mcCfg->getBlockableJobConfig())); + _mcCfg->getBlockableJobConfig(), + _mcCfg->getFlushConfig())); _mcCfg = newCfg; forwardMaintenanceConfig(); } @@ -546,7 +550,8 @@ public: cfg, _mcCfg->getAttributeUsageFilterConfig(), _mcCfg->getAttributeUsageSampleInterval(), - _mcCfg->getBlockableJobConfig())); + _mcCfg->getBlockableJobConfig(), + _mcCfg->getFlushConfig())); _mcCfg = newCfg; forwardMaintenanceConfig(); } diff --git a/searchcore/src/tests/proton/flushengine/flushengine.cpp b/searchcore/src/tests/proton/flushengine/flushengine.cpp index 5e8537b657f..d1a98f1b7d3 100644 --- a/searchcore/src/tests/proton/flushengine/flushengine.cpp +++ b/searchcore/src/tests/proton/flushengine/flushengine.cpp @@ -11,8 +11,9 @@ #include <vespa/searchcore/proton/test/dummy_flush_target.h> #include <vespa/vespalib/testkit/testapp.h> #include <vespa/vespalib/data/slime/slime.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/test/insertion_operators.h> +#include <mutex> +#include <chrono> #include <vespa/log/log.h> LOG_SETUP("flushengine_test"); @@ -122,7 +123,7 @@ public: search::SerialNum _oldestSerial; search::SerialNum _currentSerial; uint32_t _pendingDone; - vespalib::Lock _lock; + std::mutex _lock; vespalib::CountDownLatch _done; FlushDoneHistory _flushDoneHistory; @@ -167,7 +168,7 @@ public: // Called once by flush engine slave thread for each task done void taskDone() { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); ++_pendingDone; } @@ -177,7 +178,7 @@ public: void flushDone(search::SerialNum oldestSerial) override { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); LOG(info, "SimpleHandler(%s)::flushDone(%" PRIu64 ")", getName().c_str(), oldestSerial); _oldestSerial = std::max(_oldestSerial, oldestSerial); @@ -190,7 +191,7 @@ public: FlushDoneHistory getFlushDoneHistory() { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _flushDoneHistory; } }; @@ -393,13 +394,12 @@ public: } }; -class ConstantFlushStrategy : public SimpleStrategy { -public: - uint64_t _millis; - -public: - ConstantFlushStrategy(uint64_t millis) : SimpleStrategy(), _millis(millis) { } - typedef std::shared_ptr<ConstantFlushStrategy> SP; +class NoFlushStrategy : public SimpleStrategy +{ + virtual FlushContext::List getFlushTargets(const FlushContext::List &, + const flushengine::TlsStatsMap &) const override { + return FlushContext::List(); + } }; // -------------------------------------------------------------------------------- @@ -437,12 +437,38 @@ struct Fixture SimpleStrategy::SP strategy; FlushEngine engine; - Fixture(uint32_t numThreads, uint32_t idleIntervalMS) + Fixture(uint32_t numThreads, uint32_t idleIntervalMS, SimpleStrategy::SP strategy_) : tlsStatsFactory(std::make_shared<SimpleTlsStatsFactory>()), - strategy(std::make_shared<SimpleStrategy>()), + strategy(strategy_), engine(tlsStatsFactory, strategy, numThreads, idleIntervalMS) { } + + Fixture(uint32_t numThreads, uint32_t idleIntervalMS) + : Fixture(numThreads, idleIntervalMS, std::make_shared<SimpleStrategy>()) + { + } + + std::shared_ptr<SimpleHandler> + addSimpleHandler(Targets targets) + { + auto handler = std::make_shared<SimpleHandler>(targets, "handler", 20); + engine.putFlushHandler(DocTypeName("handler"), handler); + engine.start(); + return handler; + } + + void assertOldestSerial(SimpleHandler &handler, search::SerialNum expOldestSerial) + { + using namespace std::chrono_literals; + for (int pass = 0; pass < 600; ++pass) { + std::this_thread::sleep_for(100ms); + if (handler._oldestSerial == expOldestSerial) { + break; + } + } + EXPECT_EQUAL(expOldestSerial, handler._oldestSerial); + } }; @@ -717,6 +743,26 @@ TEST_F("require that state explorer can list flush targets", Fixture(1, 1)) target->_taskDone.await(LONG_TIMEOUT); } +TEST_F("require that oldest serial is updated when closing engine", Fixture(1, 100)) +{ + auto target1 = std::make_shared<SimpleTarget>("target1", 10, false); + auto handler = f.addSimpleHandler({ target1 }); + TEST_DO(f.assertOldestSerial(*handler, 10)); + target1->_proceed.countDown(); + f.engine.close(); + EXPECT_EQUAL(20u, handler->_oldestSerial); +} + +TEST_F("require that oldest serial is updated when finishing priority flush strategy", Fixture(1, 100, std::make_shared<NoFlushStrategy>())) +{ + auto target1 = std::make_shared<SimpleTarget>("target1", 10, true); + auto handler = f.addSimpleHandler({ target1 }); + TEST_DO(f.assertOldestSerial(*handler, 10)); + f.engine.setStrategy(std::make_shared<SimpleStrategy>()); + EXPECT_EQUAL(20u, handler->_oldestSerial); +} + + TEST_MAIN() { TEST_RUN_ALL(); diff --git a/searchcore/src/tests/proton/index/indexmanager_test.cpp b/searchcore/src/tests/proton/index/indexmanager_test.cpp index e8afd738e84..6a098667be8 100644 --- a/searchcore/src/tests/proton/index/indexmanager_test.cpp +++ b/searchcore/src/tests/proton/index/indexmanager_test.cpp @@ -20,7 +20,6 @@ #include <vespa/searchlib/common/serialnum.h> #include <vespa/searchlib/util/dirtraverse.h> #include <vespa/vespalib/testkit/testapp.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/util/threadstackexecutor.h> #include <vespa/vespalib/util/blockingthreadstackexecutor.h> #include <vespa/vespalib/io/fileutil.h> @@ -50,9 +49,6 @@ using search::queryeval::Source; using std::set; using std::string; using vespalib::BlockingThreadStackExecutor; -using vespalib::Gate; -using vespalib::Monitor; -using vespalib::MonitorGuard; using vespalib::ThreadStackExecutor; using namespace proton; diff --git a/searchcore/src/tests/proton/matchengine/matchengine.cpp b/searchcore/src/tests/proton/matchengine/matchengine.cpp index d2ce4b14b9c..ccae9fa3d4f 100644 --- a/searchcore/src/tests/proton/matchengine/matchengine.cpp +++ b/searchcore/src/tests/proton/matchengine/matchengine.cpp @@ -3,6 +3,9 @@ #include <vespa/vespalib/data/slime/slime.h> #include <vespa/searchlib/engine/docsumreply.h> #include <vespa/vespalib/testkit/test_kit.h> +#include <mutex> +#include <condition_variable> +#include <chrono> using namespace proton; using namespace search::engine; @@ -34,23 +37,26 @@ public: class LocalSearchClient : public SearchClient { private: - vespalib::Monitor _monitor; - SearchReply::UP _reply; + std::mutex _lock; + std::condition_variable _cond; + SearchReply::UP _reply; public: LocalSearchClient(); ~LocalSearchClient(); void searchDone(SearchReply::UP reply) override { - vespalib::MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); _reply = std::move(reply); - guard.broadcast(); + _cond.notify_all(); } SearchReply::UP getReply(uint32_t millis) { - vespalib::MonitorGuard guard(_monitor); - vespalib::TimedWaiter waiter(guard, millis); - while (_reply.get() == NULL && waiter.hasTime()) { - waiter.wait(); + std::unique_lock<std::mutex> guard(_lock); + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(millis); + while (!_reply) { + if (_cond.wait_until(guard, deadline) == std::cv_status::timeout) { + break; + } } return std::move(_reply); } diff --git a/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp b/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp index a0dd3ee6214..4ed01f8caf7 100644 --- a/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp +++ b/searchcore/src/tests/proton/metrics/job_tracked_flush/job_tracked_flush_test.cpp @@ -9,7 +9,7 @@ LOG_SETUP("job_tracked_flush_test"); #include <vespa/vespalib/testkit/testapp.h> #include <vespa/vespalib/util/closuretask.h> #include <vespa/vespalib/util/threadstackexecutor.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> using namespace proton; using namespace searchcorespi; @@ -17,7 +17,6 @@ using search::SerialNum; using test::SimpleJobTracker; using vespalib::makeTask; using vespalib::makeClosure; -using vespalib::CountDownLatch; using vespalib::Gate; using vespalib::ThreadStackExecutor; diff --git a/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp b/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp index 4dd8c43d9b2..fb0c9b1f3a9 100644 --- a/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp +++ b/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp @@ -3,14 +3,12 @@ #include <vespa/vespalib/testkit/testapp.h> #include <vespa/searchcore/proton/common/hw_info.h> #include <vespa/searchcore/proton/server/disk_mem_usage_filter.h> +#include <vespa/searchcore/proton/server/resource_usage_state.h> -using proton::DiskMemUsageFilter; -using proton::HwInfo; +using namespace proton; namespace fs = std::experimental::filesystem; -namespace { - struct Fixture { DiskMemUsageFilter _filter; @@ -18,13 +16,13 @@ struct Fixture using Config = DiskMemUsageFilter::Config; Fixture() - : _filter(HwInfo(HwInfo::Disk(100, false, false), HwInfo::Memory(64 * 1024 * 1024), HwInfo::Cpu(0))) + : _filter(HwInfo(HwInfo::Disk(100, false, false), HwInfo::Memory(1000), HwInfo::Cpu(0))) { - _filter.setDiskUsedSize(0); - _filter.setMemoryStats(vespalib::ProcessMemoryStats(10000000, - 10000001, - 10000002, - 10000003, + _filter.setDiskUsedSize(20); + _filter.setMemoryStats(vespalib::ProcessMemoryStats(297, + 298, + 299, + 300, 42)); } @@ -48,16 +46,14 @@ struct Fixture void triggerMemoryLimit() { - _filter.setMemoryStats(vespalib::ProcessMemoryStats(58720259, - 58720258, - 58720257, - 58720256, + _filter.setMemoryStats(vespalib::ProcessMemoryStats(897, + 898, + 899, + 900, 43)); } }; -} - TEST_F("Check that default filter allows write", Fixture) { f.testWrite(""); @@ -70,28 +66,40 @@ TEST_F("Check that stats are wired through", Fixture) EXPECT_EQUAL(43u, f._filter.getMemoryStats().getMappingsCount()); } +void +assertResourceUsage(double usage, double limit, double utilization, const ResourceUsageState &state) +{ + EXPECT_EQUAL(usage, state.usage()); + EXPECT_EQUAL(limit, state.limit()); + EXPECT_EQUAL(utilization, state.utilization()); +} + TEST_F("Check that disk limit can be reached", Fixture) { f._filter.setConfig(Fixture::Config(1.0, 0.8)); + TEST_DO(assertResourceUsage(0.2, 0.8, 0.25, f._filter.usageState().diskState())); f.triggerDiskLimit(); f.testWrite("diskLimitReached: { " "action: \"add more content nodes\", " "reason: \"disk used (0.9) > disk limit (0.8)\", " "stats: { " "capacity: 100, used: 90, diskUsed: 0.9, diskLimit: 0.8}}"); + TEST_DO(assertResourceUsage(0.9, 0.8, 1.125, f._filter.usageState().diskState())); } TEST_F("Check that memory limit can be reached", Fixture) { f._filter.setConfig(Fixture::Config(0.8, 1.0)); + TEST_DO(assertResourceUsage(0.3, 0.8, 0.375, f._filter.usageState().memoryState())); f.triggerMemoryLimit(); f.testWrite("memoryLimitReached: { " "action: \"add more content nodes\", " - "reason: \"memory used (0.875) > memory limit (0.8)\", " + "reason: \"memory used (0.9) > memory limit (0.8)\", " "stats: { " - "mapped: { virt: 58720259, rss: 58720258}, " - "anonymous: { virt: 58720257, rss: 58720256}, " - "physicalMemory: 67108864, memoryUsed: 0.875, memoryLimit: 0.8}}"); + "mapped: { virt: 897, rss: 898}, " + "anonymous: { virt: 899, rss: 900}, " + "physicalMemory: 1000, memoryUsed: 0.9, memoryLimit: 0.8}}"); + TEST_DO(assertResourceUsage(0.9, 0.8, 1.125, f._filter.usageState().memoryState())); } TEST_F("Check that both disk limit and memory limit can be reached", Fixture) @@ -101,11 +109,11 @@ TEST_F("Check that both disk limit and memory limit can be reached", Fixture) f.triggerDiskLimit(); f.testWrite("memoryLimitReached: { " "action: \"add more content nodes\", " - "reason: \"memory used (0.875) > memory limit (0.8)\", " + "reason: \"memory used (0.9) > memory limit (0.8)\", " "stats: { " - "mapped: { virt: 58720259, rss: 58720258}, " - "anonymous: { virt: 58720257, rss: 58720256}, " - "physicalMemory: 67108864, memoryUsed: 0.875, memoryLimit: 0.8}}, " + "mapped: { virt: 897, rss: 898}, " + "anonymous: { virt: 899, rss: 900}, " + "physicalMemory: 1000, memoryUsed: 0.9, memoryLimit: 0.8}}, " "diskLimitReached: { " "action: \"add more content nodes\", " "reason: \"disk used (0.9) > disk limit (0.8)\", " diff --git a/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp b/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp index a76596aa927..b3a7f5f525e 100644 --- a/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp +++ b/searchcore/src/tests/proton/server/memory_flush_config_updater/memory_flush_config_updater_test.cpp @@ -56,9 +56,16 @@ struct Fixture EXPECT_EQUAL(expMaxEachMemory, strategy->getConfig().maxMemoryGain); EXPECT_EQUAL(expMaxGlobalTlsSize, strategy->getConfig().maxGlobalTlsSize); } + void assertStrategyDiskConfig(double expGlobalDiskBloatFactor, double expDiskBloatFactor) { + EXPECT_APPROX(expGlobalDiskBloatFactor, strategy->getConfig().globalDiskBloatFactor, 0.00001); + EXPECT_APPROX(expDiskBloatFactor, strategy->getConfig().diskBloatFactor, 0.00001); + } void notifyDiskMemUsage(const ResourceUsageState &diskState, const ResourceUsageState &memoryState) { updater.notifyDiskMemUsage(DiskMemUsageState(diskState, memoryState)); } + void setNodeRetired(bool nodeRetired) { + updater.setNodeRetired(nodeRetired); + } }; TEST_F("require that strategy is updated when setting new config", Fixture) @@ -133,4 +140,14 @@ TEST_F("require that we must go below low watermark for memory usage before usin TEST_DO(f.assertStrategyConfig(4, 1, 20)); } +TEST_F("require that more disk bloat is allowed while node state is retired", Fixture) +{ + f.notifyDiskMemUsage(ResourceUsageState(0.7, 0.3), belowLimit()); + TEST_DO(f.assertStrategyDiskConfig(0.2, 0.2)); + f.setNodeRetired(true); + TEST_DO(f.assertStrategyDiskConfig((0.8 - 0.3 / 0.7) * 0.8, 1.0)); + f.notifyDiskMemUsage(belowLimit(), belowLimit()); + TEST_DO(f.assertStrategyDiskConfig(0.2, 0.2)); +} + TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchcore/src/tests/proton/summaryengine/summaryengine.cpp b/searchcore/src/tests/proton/summaryengine/summaryengine.cpp index db707e4aa97..4951b1cd569 100644 --- a/searchcore/src/tests/proton/summaryengine/summaryengine.cpp +++ b/searchcore/src/tests/proton/summaryengine/summaryengine.cpp @@ -9,8 +9,11 @@ #include <vespa/vespalib/util/compressor.h> #include <vespa/searchlib/common/transport.h> #include <vespa/fnet/frt/rpcrequest.h> -#include <vespa/log/log.h> +#include <mutex> +#include <condition_variable> +#include <chrono> +#include <vespa/log/log.h> LOG_SETUP("summaryengine_test"); using namespace search::engine; @@ -81,8 +84,9 @@ public: class MyDocsumClient : public DocsumClient { private: - vespalib::Monitor _monitor; - DocsumReply::UP _reply; + std::mutex _lock; + std::condition_variable _cond; + DocsumReply::UP _reply; public: MyDocsumClient(); @@ -90,16 +94,18 @@ public: ~MyDocsumClient(); void getDocsumsDone(DocsumReply::UP reply) override { - vespalib::MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); _reply = std::move(reply); - guard.broadcast(); + _cond.notify_all(); } DocsumReply::UP getReply(uint32_t millis) { - vespalib::MonitorGuard guard(_monitor); - vespalib::TimedWaiter waiter(guard, millis); - while (_reply.get() == NULL && waiter.hasTime()) { - waiter.wait(); + std::unique_lock<std::mutex> guard(_lock); + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(millis); + while (!_reply) { + if (_cond.wait_until(guard, deadline) == std::cv_status::timeout) { + break; + } } return std::move(_reply); } diff --git a/searchcore/src/vespa/searchcore/config/proton.def b/searchcore/src/vespa/searchcore/config/proton.def index c6c810ae72f..f46f6c67fd4 100644 --- a/searchcore/src/vespa/searchcore/config/proton.def +++ b/searchcore/src/vespa/searchcore/config/proton.def @@ -143,9 +143,15 @@ index.warmup.time double default=0.0 restart # Indicate if we also want warm up with full unpack, instead of only cheaper seek. index.warmup.unpack bool default=false restart -## How many flushed indexes there can be befor fusion is forced. +## How many flushed indexes there can be befor fusion is forced while node is +## not in retired state. ## Setting to 1 will force an immediate fusion. -index.maxflushed int default=2 restart +index.maxflushed int default=2 + +## How many flushed indexes there can be befor fusion is forced while node is +## in retired state. +## Setting to 1 will force an immediate fusion. +index.maxflushedretired int default=20 ## How much memory is set aside for caching. ## Now only used for caching of dictionary lookups. diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h b/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h index a19dcff025d..ca0053e0261 100644 --- a/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h +++ b/searchcore/src/vespa/searchcore/fdispatch/search/fnet_search.h @@ -329,6 +329,10 @@ public: _util.CalcHitCount(); _util.AllocAlignedHitBuf(); } + void ST_AdjustNumHits(uint32_t numHits) { + _util.SetAlignedHitCount(numHits); + _util.CalcHitCount(); + } uint32_t ST_GetAlignedSearchOffset() const { return _util.GetAlignedSearchOffset(); } uint32_t ST_GetAlignedMaxHits() const { return _util.GetAlignedMaxHits(); } uint32_t ST_GetAlignedHitCount() const { return _util.GetAlignedHitCount(); } diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp index 846d95bd722..8c4a08a3bbb 100644 --- a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp +++ b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.cpp @@ -5,6 +5,8 @@ #include "fnet_dataset.h" #include "fnet_search.h" #include <vespa/searchcore/util/stlishheap.h> +#include <vespa/vespalib/stllike/hash_set.h> +#include <vespa/vespalib/stllike/hash_set.hpp> #include <vespa/log/log.h> LOG_SETUP(".fdispatch.mergehits"); @@ -65,10 +67,17 @@ FastS_MergeCopyHit(typename T::HitType *src, dst->setDistributionKey(src->getDistributionKey()); } +struct GlobalIdHasher { + vespalib::hash_set<document::GlobalId, document::GlobalId::hash> seenSet; + bool insert(const document::GlobalId & g_id) { + return seenSet.insert(g_id).second; + } + GlobalIdHasher(size_t expected_size) : seenSet(expected_size * 3) {} +}; template <typename T, typename F> -void +size_t FastS_InternalMergeHits(FastS_HitMerger<T> *merger) { typename T::SearchType *search = merger->GetSearch(); @@ -89,18 +98,22 @@ FastS_InternalMergeHits(FastS_HitMerger<T> *merger) sortItr = sortRef; } + GlobalIdHasher seenGids(end - beg); + FastS_make_heap(heap, heapSize, FastS_MergeCompare<T, F>); - while (pt < end) { + while ((pt < end) && (heapSize > 0)) { node = *heap; - FastS_assert(heapSize > 0); + bool useHit = seenGids.insert(node->NT_GetHit()->HT_GetGlobalID()); if (F::UseSortData()) { - if (!F::DropSortData()) { + if (!F::DropSortData() && useHit) { FastS_MergeCopySortData<T>(node, sortItr++, sortDataLen); } node->NT_GetSortDataIterator()->Next(); } - FastS_MergeCopyHit<T>(node->NT_GetHit(), pt++); + if (useHit) { + FastS_MergeCopyHit<T>(node->NT_GetHit(), pt++); + } node->NT_NextHit(); if (node->NT_GetNumHitsLeft() > 0) { FastS_pop_push_heap(heap, heapSize, node, FastS_MergeCompare<T, F>); @@ -108,9 +121,13 @@ FastS_InternalMergeHits(FastS_HitMerger<T> *merger) FastS_pop_heap(heap, heapSize--, FastS_MergeCompare<T, F>); } } + if (pt != end) { + LOG(warning, "Duplicate removal lead to %zd missing hits (wanted %zd, got %zd)", + end - pt, end - beg, pt - beg); + } merger->SetLastNode(node); // source of last hit if (F::UseSortData()) { - FastS_assert(F::DropSortData() || sortItr == sortRef + (end - beg)); + FastS_assert(F::DropSortData() || sortItr == sortRef + (pt - beg)); } // generate merged sort data @@ -124,16 +141,17 @@ FastS_InternalMergeHits(FastS_HitMerger<T> *merger) char *sortData = search->ST_GetSortData(); sortItr = sortRef; - for (uint32_t residue = (end - beg); residue > 0; residue--) { + for (uint32_t residue = (pt - beg); residue > 0; residue--) { *sortIdx++ = offset; memcpy(sortData + offset, sortItr->_buf, sortItr->_len); offset += sortItr->_len; sortItr++; } *sortIdx = offset; - FastS_assert(sortItr == sortRef + (end - beg)); + FastS_assert(sortItr == sortRef + (pt - beg)); FastS_assert(offset == sortDataLen); } + return (pt - beg); } //----------------------------------------------------------------------------- @@ -219,16 +237,17 @@ FastS_HitMerger<T>::MergeHits() // do actual merging by invoking templated function if (useSortData) { if (dropSortData) { - FastS_InternalMergeHits + numDocs = FastS_InternalMergeHits <T, FastS_MergeFeatures<true, true> >(this); } else { - FastS_InternalMergeHits + numDocs = FastS_InternalMergeHits <T, FastS_MergeFeatures<true, false> >(this); } } else { - FastS_InternalMergeHits + numDocs = FastS_InternalMergeHits <T, FastS_MergeFeatures<false, false> >(this); } + _search->ST_AdjustNumHits(numDocs); // detect incomplete/fuzzy results if (_search->ST_ShouldLimitHitsPerNode()) { diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h index 79157acb175..306229b4730 100644 --- a/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h +++ b/searchcore/src/vespa/searchcore/fdispatch/search/mergehits.h @@ -64,6 +64,7 @@ struct FastS_MergeHits_DummySearch bool ST_ShouldDropSortData() { return false; } bool ST_ShouldLimitHitsPerNode() { return false; } void ST_SetNumHits(uint32_t numHits) { (void) numHits; } + void ST_AdjustNumHits(uint32_t nH) { (void) nH; } uint32_t ST_GetAlignedSearchOffset() { return 0; } uint32_t ST_GetAlignedMaxHits() { return 0; } uint32_t ST_GetAlignedHitCount() { return 0; } diff --git a/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp b/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp index c2c26e52a21..d9a0ff3d8dd 100644 --- a/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp +++ b/searchcore/src/vespa/searchcore/proton/attribute/exclusive_attribute_read_accessor.cpp @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "exclusive_attribute_read_accessor.h" +#include <vespa/vespalib/util/gate.h> #include <vespa/searchlib/attribute/attributevector.h> #include <vespa/searchlib/common/isequencedtaskexecutor.h> diff --git a/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp b/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp index 49f70092d29..f3afab96cf5 100644 --- a/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp +++ b/searchcore/src/vespa/searchcore/proton/common/eventlogger.cpp @@ -303,4 +303,18 @@ EventLogger::loadDocumentStoreComplete(const vespalib::string &subDbName, int64_ loadComponentComplete(subDbName, "documentstore", elapsedTimeMs); } +void +EventLogger::transactionLogPruneComplete(const string &domainName, SerialNum prunedSerial) +{ + JSONStringer jstr; + jstr.beginObject(); + jstr.appendKey("domain").appendString(domainName); + jstr.appendKey("serialnum") + .beginObject() + .appendKey("pruned").appendInt64(prunedSerial) + .endObject(); + jstr.endObject(); + EV_STATE("transactionlog.prune.complete", jstr.toString().c_str()); +} + } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/common/eventlogger.h b/searchcore/src/vespa/searchcore/proton/common/eventlogger.h index ab3793eb677..6ba8852496e 100644 --- a/searchcore/src/vespa/searchcore/proton/common/eventlogger.h +++ b/searchcore/src/vespa/searchcore/proton/common/eventlogger.h @@ -50,6 +50,7 @@ public: static void loadDocumentMetaStoreComplete(const vespalib::string &subDbName, int64_t elapsedTimeMs); static void loadDocumentStoreStart(const vespalib::string &subDbName); static void loadDocumentStoreComplete(const vespalib::string &subDbName, int64_t elapsedTimeMs); + static void transactionLogPruneComplete(const string &domainName, SerialNum prunedSerial); }; } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/common/feedtoken.h b/searchcore/src/vespa/searchcore/proton/common/feedtoken.h index bc7e249ca3b..ab3fdea3345 100644 --- a/searchcore/src/vespa/searchcore/proton/common/feedtoken.h +++ b/searchcore/src/vespa/searchcore/proton/common/feedtoken.h @@ -3,7 +3,6 @@ #include <vespa/persistence/spi/persistenceprovider.h> #include <vespa/searchlib/common/idestructorcallback.h> -#include <vespa/vespalib/util/sync.h> #include <atomic> namespace proton { diff --git a/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp b/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp index 6b5d89cbd22..bddbfed371f 100644 --- a/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp +++ b/searchcore/src/vespa/searchcore/proton/common/handlermap.hpp @@ -3,7 +3,6 @@ #include "doctypename.h" #include <vespa/vespalib/util/exceptions.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/util/sequence.h> #include <vespa/vespalib/stllike/string.h> #include <vespa/vespalib/stllike/hash_map.h> diff --git a/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp b/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp index 0915c11acfa..da45953e7b9 100644 --- a/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp +++ b/searchcore/src/vespa/searchcore/proton/common/selectpruner.cpp @@ -21,7 +21,6 @@ using document::select::Or; using document::select::ArithmeticValueNode; using document::select::FunctionValueNode; using document::select::IdValueNode; -using document::select::SearchColumnValueNode; using document::select::FieldValueNode; using document::select::FloatValueNode; using document::select::VariableValueNode; @@ -235,7 +234,7 @@ SelectPruner::visitDocumentType(const DocType &expr) _inverted = true; res = !res; } - _node.reset(new Constant(res ? "true" : "false")); + _node.reset(new Constant(res)); _resultSet.add(res ? Result::True : Result::False); _priority = DocumentTypePriority; } @@ -411,7 +410,7 @@ SelectPruner::visitFieldValueNode(const FieldValueNode &expr) if (!_hasFields) { // If we're working on removed document sub db then we have no fields. _constVal = true; - _valueNode.reset(new NullValueNode("null")); + _valueNode.reset(new NullValueNode()); _priority = NullValPriority; return; } @@ -511,7 +510,7 @@ SelectPruner::setTernaryConst(bool val) { _constVal = true; _priority = ConstPriority; - _node.reset(new Constant(val ? "true" : "false")); + _node.reset(new Constant(val)); } void diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp index 026cbc00604..6667db02173 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp @@ -4,6 +4,8 @@ #include <vespa/searchlib/query/queryterm.h> #include <vespa/searchlib/attribute/attributevector.h> #include <vespa/searchlib/attribute/singlesmallnumericattribute.h> +#include <mutex> + #include <vespa/log/log.h> LOG_SETUP(".proton.documentmetastore.lid_allocator"); @@ -223,7 +225,7 @@ class BlackListBlueprint : public SimpleLeafBlueprint { private: AttributeVector::SearchContext::UP _searchCtx; - vespalib::Lock _lock; + mutable std::mutex _lock; mutable std::vector<search::fef::TermFieldMatchData *> _matchDataVector; virtual SearchIterator::UP @@ -235,7 +237,7 @@ private: search::fef::TermFieldMatchData *tfmd = new search::fef::TermFieldMatchData; { - vespalib::LockGuard lock(_lock); + std::lock_guard<std::mutex> lock(_lock); _matchDataVector.push_back(tfmd); } return _searchCtx->createIterator(tfmd, strict); diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp index fb25f8cf161..95b3008985b 100644 --- a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp +++ b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.cpp @@ -12,10 +12,10 @@ #include <vespa/log/log.h> LOG_SETUP(".proton.flushengine.flushengine"); -using vespalib::MonitorGuard; typedef vespalib::Executor::Task Task; using searchcorespi::IFlushTarget; using searchcorespi::FlushStats; +using namespace std::chrono_literals; namespace proton { @@ -71,11 +71,13 @@ FlushEngine::FlushEngine(std::shared_ptr<flushengine::ITlsStatsFactory> _strategy(strategy), _priorityStrategy(), _executor(numThreads, 128 * 1024), - _monitor(), + _lock(), + _cond(), _handlers(), _flushing(), + _setStrategyLock(), _strategyLock(), - _strategyMonitor(), + _strategyCond(), _tlsStatsFactory(tlsStatsFactory), _pendingPrune() { @@ -100,10 +102,10 @@ FlushEngine & FlushEngine::close() { { - MonitorGuard strategyGuard(_strategyMonitor); - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> strategyGuard(_strategyLock); + std::lock_guard<std::mutex> guard(_lock); _closed = true; - guard.broadcast(); + _cond.notify_all(); } _threadPool.Close(); _executor.shutdown(); @@ -120,13 +122,13 @@ FlushEngine::triggerFlush() void FlushEngine::kick() { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); LOG(debug, "Kicking flush engine"); - guard.broadcast(); + _cond.notify_all(); } bool -FlushEngine::canFlushMore(const MonitorGuard & guard) const +FlushEngine::canFlushMore(const std::unique_lock<std::mutex> &guard) const { (void) guard; return _maxConcurrent > _flushing.size(); @@ -135,12 +137,12 @@ FlushEngine::canFlushMore(const MonitorGuard & guard) const bool FlushEngine::wait(size_t minimumWaitTimeIfReady) { - MonitorGuard guard(_monitor); + std::unique_lock<std::mutex> guard(_lock); if ( (minimumWaitTimeIfReady > 0) && canFlushMore(guard) && _pendingPrune.empty()) { - guard.wait(minimumWaitTimeIfReady); + _cond.wait_for(guard, std::chrono::milliseconds(minimumWaitTimeIfReady)); } while ( ! canFlushMore(guard) && _pendingPrune.empty()) { - guard.wait(1000); // broadcast when flush done + _cond.wait_for(guard, 1s); // broadcast when flush done } return !_closed; } @@ -167,6 +169,8 @@ FlushEngine::Run(FastOS_ThreadInterface *thread, void *arg) } LOG(debug, "Making another wait(idle=%s, timeMS=%d) last was '%s'", shouldIdle ? "true" : "false", shouldIdle ? _idleIntervalMS : 0, prevFlushName.c_str()); } + _executor.sync(); + prune(); } bool @@ -174,7 +178,7 @@ FlushEngine::prune() { std::set<IFlushHandler::SP> toPrune; { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); if (_pendingPrune.empty()) { return false; } @@ -187,7 +191,7 @@ FlushEngine::prune() return true; } -bool FlushEngine::isFlushing(const MonitorGuard & guard, const vespalib::string & name) const +bool FlushEngine::isFlushing(const std::lock_guard<std::mutex> & guard, const vespalib::string & name) const { (void) guard; for(const auto & it : _flushing) { @@ -203,7 +207,7 @@ FlushEngine::getTargetList(bool includeFlushingTargets) const { FlushContext::List ret; { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); for (const auto & it : _handlers) { IFlushHandler & handler(*it.second); search::SerialNum serial(handler.getCurrentSerialNumber()); @@ -227,12 +231,12 @@ FlushEngine::getTargetList(bool includeFlushingTargets) const } std::pair<FlushContext::List,bool> -FlushEngine::getSortedTargetList(MonitorGuard &strategyGuard) const +FlushEngine::getSortedTargetList() { - (void) strategyGuard; FlushContext::List unsortedTargets = getTargetList(false); - std::pair<FlushContext::List, bool> ret; flushengine::TlsStatsMap tlsStatsMap(_tlsStatsFactory->create()); + std::lock_guard<std::mutex> strategyGuard(_strategyLock); + std::pair<FlushContext::List, bool> ret; if (_priorityStrategy) { ret = std::make_pair(_priorityStrategy->getFlushTargets(unsortedTargets, tlsStatsMap), true); } else { @@ -291,14 +295,15 @@ FlushEngine::flushAll(const FlushContext::List &lst) vespalib::string FlushEngine::flushNextTarget(const vespalib::string & name) { - MonitorGuard strategyGuard(_strategyMonitor); - std::pair<FlushContext::List,bool> lst = getSortedTargetList(strategyGuard); + std::pair<FlushContext::List,bool> lst = getSortedTargetList(); if (lst.second) { // Everything returned from a priority strategy should be flushed flushAll(lst.first); _executor.sync(); + prune(); + std::lock_guard<std::mutex> strategyGuard(_strategyLock); _priorityStrategy.reset(); - strategyGuard.broadcast(); + _strategyCond.notify_all(); return ""; } if (lst.first.empty()) { @@ -340,7 +345,7 @@ FlushEngine::flushDone(const FlushContext &ctx, uint32_t taskId) { fastos::TimeStamp duration; { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); duration = fastos::TimeStamp(fastos::ClockSystem::now()) - _flushing[taskId].getStart(); } if (LOG_WOULD_LOG(event)) { @@ -351,20 +356,20 @@ FlushEngine::flushDone(const FlushContext &ctx, uint32_t taskId) stats.getPathElementsToLog()); } LOG(debug, "FlushEngine::flushDone(taskId='%d') took '%f' secs", taskId, duration.sec()); - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); _flushing.erase(taskId); assert(ctx.getHandler()); if (_handlers.hasHandler(ctx.getHandler())) { _pendingPrune.insert(ctx.getHandler()); } - guard.broadcast(); + _cond.notify_all(); } IFlushHandler::SP FlushEngine::putFlushHandler(const DocTypeName &docTypeName, const IFlushHandler::SP &flushHandler) { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); IFlushHandler::SP result(_handlers.putHandler(docTypeName, flushHandler)); if (result) { _pendingPrune.erase(result); @@ -376,14 +381,14 @@ FlushEngine::putFlushHandler(const DocTypeName &docTypeName, IFlushHandler::SP FlushEngine::getFlushHandler(const DocTypeName &docTypeName) const { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); return _handlers.getHandler(docTypeName); } IFlushHandler::SP FlushEngine::removeFlushHandler(const DocTypeName &docTypeName) { - MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); IFlushHandler::SP result(_handlers.removeHandler(docTypeName)); _pendingPrune.erase(result); return std::move(result); @@ -393,7 +398,7 @@ FlushEngine::FlushMetaSet FlushEngine::getCurrentlyFlushingSet() const { FlushMetaSet s; - vespalib::LockGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); for (const auto & it : _flushing) { s.insert(it.second); } @@ -405,7 +410,7 @@ FlushEngine::initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP { uint32_t taskId(0); { - vespalib::LockGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); taskId = _taskId++; vespalib::string name(FlushContext::createName(*handler, *target)); FlushInfo flush(taskId, target, name); @@ -419,19 +424,19 @@ FlushEngine::initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP void FlushEngine::setStrategy(IFlushStrategy::SP strategy) { - vespalib::LockGuard strategyLock(_strategyLock); - MonitorGuard strategyGuard(_strategyMonitor); + std::lock_guard<std::mutex> setStrategyGuard(_setStrategyLock); + std::unique_lock<std::mutex> strategyGuard(_strategyLock); if (_closed) { return; } assert(!_priorityStrategy); _priorityStrategy = strategy; { - MonitorGuard guard(_monitor); - guard.broadcast(); + std::lock_guard<std::mutex> guard(_lock); + _cond.notify_all(); } while (_priorityStrategy) { - strategyGuard.wait(); + _strategyCond.wait(strategyGuard); } } diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h index 7f4698610c8..19175f9ce2a 100644 --- a/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h +++ b/searchcore/src/vespa/searchcore/proton/flushengine/flushengine.h @@ -5,10 +5,11 @@ #include "iflushstrategy.h" #include <vespa/searchcore/proton/common/handlermap.hpp> #include <vespa/searchcore/proton/common/doctypename.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/util/threadstackexecutor.h> #include <vespa/fastos/thread.h> #include <set> +#include <mutex> +#include <condition_variable> namespace proton { @@ -53,16 +54,18 @@ private: IFlushStrategy::SP _strategy; mutable IFlushStrategy::SP _priorityStrategy; vespalib::ThreadStackExecutor _executor; - vespalib::Monitor _monitor; + mutable std::mutex _lock; + std::condition_variable _cond; FlushHandlerMap _handlers; FlushMap _flushing; - vespalib::Lock _strategyLock; // serialize setStrategy calls - vespalib::Monitor _strategyMonitor; + std::mutex _setStrategyLock; // serialize setStrategy calls + std::mutex _strategyLock; + std::condition_variable _strategyCond; std::shared_ptr<flushengine::ITlsStatsFactory> _tlsStatsFactory; std::set<IFlushHandler::SP> _pendingPrune; FlushContext::List getTargetList(bool includeFlushingTargets) const; - std::pair<FlushContext::List,bool> getSortedTargetList(vespalib::MonitorGuard &strategyGuard) const; + std::pair<FlushContext::List,bool> getSortedTargetList(); FlushContext::SP initNextFlush(const FlushContext::List &lst); vespalib::string flushNextTarget(const vespalib::string & name); void flushAll(const FlushContext::List &lst); @@ -70,9 +73,9 @@ private: uint32_t initFlush(const FlushContext &ctx); uint32_t initFlush(const IFlushHandler::SP &handler, const IFlushTarget::SP &target); void flushDone(const FlushContext &ctx, uint32_t taskId); - bool canFlushMore(const vespalib::MonitorGuard & guard) const; + bool canFlushMore(const std::unique_lock<std::mutex> &guard) const; bool wait(size_t minimumWaitTimeIfReady); - bool isFlushing(const vespalib::MonitorGuard & guard, const vespalib::string & name) const; + bool isFlushing(const std::lock_guard<std::mutex> &guard, const vespalib::string & name) const; friend class FlushTask; friend class FlushEngineExplorer; diff --git a/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h b/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h index 421b176bfec..3a24330f8ec 100644 --- a/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h +++ b/searchcore/src/vespa/searchcore/proton/flushengine/iflushhandler.h @@ -65,7 +65,7 @@ public: /** * This method is called after a flush has been completed. All transactions * up to the given serial number can be pruned from the domain of this - * handler. This method is called by an arbitrary worker thread. + * handler. This method is called by the flush scheduler thread. * * @param flushedSerial Serial number flushed for all flush * targets belonging to this handler. diff --git a/searchcore/src/vespa/searchcore/proton/index/indexmanager.h b/searchcore/src/vespa/searchcore/proton/index/indexmanager.h index 00dd4befed7..1607c19611a 100644 --- a/searchcore/src/vespa/searchcore/proton/index/indexmanager.h +++ b/searchcore/src/vespa/searchcore/proton/index/indexmanager.h @@ -115,6 +115,9 @@ public: virtual void setSchema(const Schema &schema, SerialNum serialNum) override { _maintainer.setSchema(schema, serialNum); } + virtual void setMaxFlushed(uint32_t maxFlushed) override { + _maintainer.setMaxFlushed(maxFlushed); + } }; } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp index adb14b11c71..05dc75146c7 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.cpp @@ -59,7 +59,7 @@ AttributeLimiter::toString(DiversityCutoffStrategy strategy) SearchIterator::UP AttributeLimiter::create_search(size_t want_hits, size_t max_group_size, bool strictSearch) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); const uint32_t my_field_id = 0; search::fef::MatchDataLayout layout; auto my_handle = layout.allocTermField(my_field_id); diff --git a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h index 67180da6a10..95f7bf4e42b 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h +++ b/searchcore/src/vespa/searchcore/proton/matching/attribute_limiter.h @@ -4,11 +4,11 @@ #include <vespa/searchlib/queryeval/searchable.h> #include <vespa/vespalib/stllike/string.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/searchlib/queryeval/searchiterator.h> #include <vespa/searchlib/queryeval/blueprint.h> #include <vespa/searchlib/fef/matchdata.h> +#include <mutex> namespace proton { namespace matching { @@ -43,7 +43,7 @@ private: vespalib::string _attribute_name; bool _descending; vespalib::string _diversity_attribute; - vespalib::Lock _lock; + std::mutex _lock; std::vector<search::fef::MatchData::UP> _match_datas; search::queryeval::Blueprint::UP _blueprint; ssize_t _estimatedHits; diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h index e9e69c35bd4..a0aefd150f5 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h +++ b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limit_calculator.h @@ -4,7 +4,6 @@ #include "isearchcontext.h" #include <vespa/vespalib/stllike/string.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/searchlib/queryeval/searchiterator.h> #include <vespa/searchlib/queryeval/blueprint.h> diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h index bc7a8038974..165762d5356 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h +++ b/searchcore/src/vespa/searchcore/proton/matching/match_phase_limiter.h @@ -7,7 +7,6 @@ #include <vespa/searchlib/queryeval/searchable.h> #include <vespa/vespalib/stllike/string.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/searchlib/queryeval/searchiterator.h> #include <vespa/searchlib/queryeval/blueprint.h> #include <atomic> diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp index 32775d7619a..bbe6f604e4e 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp @@ -152,7 +152,7 @@ Matcher::Matcher(const search::index::Schema &schema, MatchingStats Matcher::getStats() { - vespalib::LockGuard guard(_statsLock); + std::lock_guard<std::mutex> guard(_statsLock); MatchingStats stats = std::move(_stats); _stats = std::move(MatchingStats()); _stats.softDoomFactor(stats.softDoomFactor()); @@ -268,9 +268,9 @@ Matcher::match(const SearchRequest &request, ResultProcessor::Result::UP result = master.match(params, limitedThreadBundle, *mtf, rp, _distributionKey, numSearchPartitions); my_stats = MatchMaster::getStats(std::move(master)); - size_t estimate = std::min(static_cast<size_t>(metaStore.getCommittedDocIdLimit()), - mtf->match_limiter().getDocIdSpaceEstimate()); + bool wasLimited = mtf->match_limiter().was_limited(); + size_t spaceEstimate = mtf->match_limiter().getDocIdSpaceEstimate(); uint32_t estHits = mtf->estimate().estHits; if (shouldCacheSearchSession && ((result->_numFs4Hits != 0) || shouldCacheGroupingSession)) { SearchSession::SP session = std::make_shared<SearchSession>(sessionId, request.getTimeOfDoom(), @@ -281,16 +281,32 @@ Matcher::match(const SearchRequest &request, reply = std::move(result->_reply); SearchReply::Coverage & coverage = reply->coverage; if (wasLimited) { + LOG(debug, "was limited, degraded from match phase"); coverage.degradeMatchPhase(); } if (my_stats.softDoomed()) { + LOG(debug, "soft doomed, degraded from timeout"); coverage.degradeTimeout(); } - coverage.setActive(metaStore.getNumActiveLids()); + uint32_t numActiveLids = metaStore.getNumActiveLids(); + // note: this is actually totalSpace+1, since 0 is reserved + uint32_t totalSpace = metaStore.getCommittedDocIdLimit(); + LOG(debug, "docid limit = %d", totalSpace); + LOG(debug, "num active lids = %d", numActiveLids); + LOG(debug, "space Estimate = %zd", spaceEstimate); + if (spaceEstimate >= totalSpace) { + // estimate is too high, clamp it + spaceEstimate = totalSpace; + } else { + // account for docid 0 reserved + spaceEstimate += 1; + } + size_t covered = (spaceEstimate * numActiveLids) / totalSpace; + LOG(debug, "covered = %zd", covered); + coverage.setActive(numActiveLids); //TODO this should be calculated with ClusterState calculator. - coverage.setSoonActive(metaStore.getNumActiveLids()); - coverage.setCovered(std::min(static_cast<size_t>(metaStore.getNumActiveLids()), - (estimate * metaStore.getNumActiveLids())/metaStore.getCommittedDocIdLimit())); + coverage.setSoonActive(numActiveLids); + coverage.setCovered(covered); LOG(debug, "numThreadsPerSearch = %zu. Configured = %d, estimated hits=%d, totalHits=%ld", numThreadsPerSearch, _rankSetup->getNumThreadsPerSearch(), estHits, reply->totalHitCount); } @@ -299,7 +315,7 @@ Matcher::match(const SearchRequest &request, { fastos::TimeStamp softLimit = uint64_t((1.0 - _rankSetup->getSoftTimeoutTailCost()) * request.getTimeout()); fastos::TimeStamp duration = request.getTimeUsed(); - vespalib::LockGuard guard(_statsLock); + std::lock_guard<std::mutex> guard(_statsLock); _stats.add(my_stats); if (my_stats.softDoomed()) { LOG(info, "Triggered softtimeout limit=%1.3f and duration=%1.3f", softLimit.sec(), duration.sec()); diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.h b/searchcore/src/vespa/searchcore/proton/matching/matcher.h index 42a74e3c29b..8415f659473 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/matcher.h +++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.h @@ -15,8 +15,8 @@ #include <vespa/searchlib/query/base.h> #include <vespa/vespalib/util/clock.h> #include <vespa/vespalib/util/closure.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/util/thread_bundle.h> +#include <mutex> namespace search { namespace grouping { @@ -52,7 +52,7 @@ private: search::fef::BlueprintFactory _blueprintFactory; search::fef::RankSetup::SP _rankSetup; ViewResolver _viewResolver; - vespalib::Lock _statsLock; + std::mutex _statsLock; MatchingStats _stats; const vespalib::Clock &_clock; QueryLimiter &_queryLimiter; diff --git a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp index 0184d3634de..0d985496d41 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.cpp @@ -1,5 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "querylimiter.h" +#include <chrono> namespace proton { namespace matching { @@ -18,11 +19,11 @@ QueryLimiter::LimitedToken::~LimitedToken() void QueryLimiter::grabToken(const Doom & doom) { - vespalib::MonitorGuard guard(_monitor); + std::unique_lock<std::mutex> guard(_lock); while ((_maxThreads > 0) && (_activeThreads >= _maxThreads) && !doom.doom()) { int left = doom.left().ms(); if (left > 0) { - guard.wait(left); + _cond.wait_for(guard, std::chrono::milliseconds(left)); } } _activeThreads++; @@ -31,13 +32,14 @@ QueryLimiter::grabToken(const Doom & doom) void QueryLimiter::releaseToken() { - vespalib::MonitorGuard guard(_monitor); + std::lock_guard<std::mutex> guard(_lock); _activeThreads--; - guard.signal(); + _cond.notify_one(); } QueryLimiter::QueryLimiter() : - _monitor(), + _lock(), + _cond(), _activeThreads(0), _maxThreads(-1), _coverage(1.0), diff --git a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h index e34ae303df2..fbe8526b051 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h +++ b/searchcore/src/vespa/searchcore/proton/matching/querylimiter.h @@ -3,8 +3,9 @@ #pragma once #include <memory> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/util/doom.h> +#include <mutex> +#include <condition_variable> namespace proton { namespace matching { @@ -35,7 +36,8 @@ private: }; void grabToken(const Doom & doom); void releaseToken(); - vespalib::Monitor _monitor; + std::mutex _lock; + std::condition_variable _cond; volatile int _activeThreads; // These are updated asynchronously at reconfig. diff --git a/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp b/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp index b6c6c798506..afaf61cab5d 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/sessionmanager.cpp @@ -3,7 +3,7 @@ #include "sessionmanager.h" #include <vespa/vespalib/stllike/lrucache_map.hpp> #include <vespa/vespalib/stllike/hash_map.hpp> -#include <vespa/vespalib/util/sync.h> +#include <mutex> #include <algorithm> #include <vespa/log/log.h> @@ -18,7 +18,7 @@ using Stats = SessionManager::Stats; struct SessionCacheBase { protected: Stats _stats; - vespalib::Lock _lock; + mutable std::mutex _lock; void entryDropped(const SessionId &id); ~SessionCacheBase() {} @@ -32,7 +32,7 @@ struct SessionCache : SessionCacheBase { SessionCache(uint32_t max_size) : _cache(max_size) {} void insert(EntryUP session) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); const SessionId &id(session->getSessionId()); if (_cache.size() >= _cache.capacity()) { entryDropped(id); @@ -41,7 +41,7 @@ struct SessionCache : SessionCacheBase { _stats.numInsert++; } EntryUP pick(const SessionId & id) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); EntryUP ret; if (_cache.hasKey(id)) { _stats.numPick++; @@ -56,7 +56,7 @@ struct SessionCache : SessionCacheBase { } std::vector<EntryUP> stealTimedOutSessions(fastos::TimeStamp currentTime) { std::vector<EntryUP> toDestruct; - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); toDestruct.reserve(_cache.size()); for (auto it(_cache.begin()), mt(_cache.end()); it != mt;) { auto &session = *it; @@ -71,14 +71,14 @@ struct SessionCache : SessionCacheBase { return toDestruct; } Stats getStats() { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); Stats stats = _stats; stats.numCached = _cache.size(); _stats = Stats(); return stats; } bool empty() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _cache.empty(); } }; @@ -89,13 +89,13 @@ struct SessionMap : SessionCacheBase { vespalib::hash_map<SessionId, EntrySP> _map; void insert(EntrySP session) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); const SessionId &id(session->getSessionId()); _map.insert(std::make_pair(id, session)); _stats.numInsert++; } EntrySP pick(const SessionId & id) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); auto it = _map.find(id); if (it != _map.end()) { _stats.numPick++; @@ -110,7 +110,7 @@ struct SessionMap : SessionCacheBase { std::vector<EntrySP> stealTimedOutSessions(fastos::TimeStamp currentTime) { std::vector<EntrySP> toDestruct; std::vector<SessionId> keys; - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); keys.reserve(_map.size()); toDestruct.reserve(_map.size()); for (auto & it : _map) { @@ -128,23 +128,23 @@ struct SessionMap : SessionCacheBase { return toDestruct; } Stats getStats() { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); Stats stats = _stats; stats.numCached = _map.size(); _stats = Stats(); return stats; } size_t size() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _map.size(); } bool empty() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _map.empty(); } template <typename F> void each(F f) const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); for (const auto &entry: _map) { f(*entry.second); } diff --git a/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h b/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h index 4dfcbe89d68..67f8e6dcacf 100644 --- a/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h +++ b/searchcore/src/vespa/searchcore/proton/metrics/documentdb_job_trackers.h @@ -4,7 +4,6 @@ #include "documentdb_tagged_metrics.h" #include "job_tracker.h" #include <vespa/searchcorespi/flush/iflushtarget.h> -#include <vespa/vespalib/util/sync.h> #include <chrono> #include <mutex> diff --git a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp index a699cf491e2..d944287243a 100644 --- a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp +++ b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.cpp @@ -7,7 +7,9 @@ namespace proton { ResourceUsageMetrics::ResourceUsageMetrics(metrics::MetricSet *parent) : MetricSet("resource_usage", "", "Usage metrics for various resources in this search engine", parent), disk("disk", "", "The relative amount of disk space used on this machine (value in the range [0, 1])", this), + diskUtilization("disk_utilization", "", "The relative amount of disk used compared to the disk resource limit", this), memory("memory", "", "The relative amount of memory used by this process (value in the range [0, 1])", this), + memoryUtilization("memory_utilization", "", "The relative amount of memory used compared to the memory resource limit", this), memoryMappings("memory_mappings", "", "The number of mapped memory areas", this), openFileDescriptors("open_file_descriptors", "", "The number of open files", this), feedingBlocked("feeding_blocked", "", "Whether feeding is blocked due to resource limits being reached (value is either 0 or 1)", this) diff --git a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h index 698ba8029bd..497a0fe2ba0 100644 --- a/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h +++ b/searchcore/src/vespa/searchcore/proton/metrics/resource_usage_metrics.h @@ -12,7 +12,9 @@ namespace proton { struct ResourceUsageMetrics : metrics::MetricSet { metrics::DoubleValueMetric disk; + metrics::DoubleValueMetric diskUtilization; metrics::DoubleValueMetric memory; + metrics::DoubleValueMetric memoryUtilization; metrics::LongValueMetric memoryMappings; metrics::LongValueMetric openFileDescriptors; metrics::LongValueMetric feedingBlocked; diff --git a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp index 00d4532a44e..00a7e9b9140 100644 --- a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp +++ b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp @@ -12,7 +12,8 @@ TransLogServerMetrics::DomainMetrics::DomainMetrics(metrics::MetricSet *parent, : metrics::MetricSet("transactionlog", {{"documenttype", documentType}}, "Transaction log metrics for a document type", parent), entries("entries", "", "The current number of entries in the transaction log", this), - diskUsage("disk_usage", "", "The disk usage (in bytes) of the transaction log", this) + diskUsage("disk_usage", "", "The disk usage (in bytes) of the transaction log", this), + replayTime("replay_time", "", "The replay time (in seconds) of the transaction log during start-up", this) { } @@ -23,6 +24,7 @@ TransLogServerMetrics::DomainMetrics::update(const DomainInfo &stats) { entries.set(stats.numEntries); diskUsage.set(stats.byteSize); + replayTime.set(stats.maxSessionRunTime.count()); } void diff --git a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h index c4620f46f4f..830d643e93e 100644 --- a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h +++ b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.h @@ -17,6 +17,7 @@ public: { metrics::LongValueMetric entries; metrics::LongValueMetric diskUsage; + metrics::DoubleValueMetric replayTime; typedef std::unique_ptr<DomainMetrics> UP; DomainMetrics(metrics::MetricSet *parent, const vespalib::string &documentType); diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp index 12ba5e7ab29..96025f5eaad 100644 --- a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp +++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.cpp @@ -20,7 +20,7 @@ void TransportLatch::send(ResultUP result, bool documentWasFound) { { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); if (!_result) { _result = std::move(result); } else if (result->hasError()) { diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h index 10dae553a80..2fb55b14fdf 100644 --- a/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h +++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/transport_latch.h @@ -4,6 +4,8 @@ #include <vespa/persistence/spi/result.h> #include <vespa/searchcore/proton/common/feedtoken.h> #include <vespa/vespalib/util/sequence.h> +#include <vespa/vespalib/util/count_down_latch.h> +#include <mutex> namespace proton { @@ -17,7 +19,7 @@ private: using UpdateResult = storage::spi::UpdateResult; using RemoveResult = storage::spi::RemoveResult; vespalib::CountDownLatch _latch; - vespalib::Lock _lock; + std::mutex _lock; ResultUP _result; public: diff --git a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt index 508a0208e70..8ee6a4f73ae 100644 --- a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt @@ -14,6 +14,7 @@ vespa_add_library(searchcore_server STATIC disk_mem_usage_forwarder.cpp docstorevalidator.cpp document_db_explorer.cpp + document_db_flush_config.cpp document_db_maintenance_config.cpp document_meta_store_read_guards.cpp document_scan_iterator.cpp diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp index 32b2b6aaba6..d53adf19f76 100644 --- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.cpp @@ -209,18 +209,11 @@ DiskMemUsageFilter::getConfig() const return _config; } -double -DiskMemUsageFilter::getMemoryUsedRatio() const -{ - Guard guard(_lock); - return getMemoryUsedRatio(guard); -} - -double -DiskMemUsageFilter::getDiskUsedRatio() const +DiskMemUsageState +DiskMemUsageFilter::usageState() const { Guard guard(_lock); - return getDiskUsedRatio(guard); + return _dmstate; } bool diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h index 4906348a54d..982e3bba6fd 100644 --- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h +++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h @@ -64,8 +64,7 @@ public: uint64_t getDiskUsedSize() const; Config getConfig() const; const HwInfo &getHwInfo() const { return _hwInfo; } - double getMemoryUsedRatio() const; - double getDiskUsedRatio() const; + DiskMemUsageState usageState() const; bool acceptWriteOperation() const override; State getAcceptState() const override; void addDiskMemUsageListener(IDiskMemUsageListener *listener) override; diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp new file mode 100644 index 00000000000..d11b8ee397d --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.cpp @@ -0,0 +1,24 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "document_db_maintenance_config.h" + +namespace proton { + +DocumentDBFlushConfig::DocumentDBFlushConfig() + : DocumentDBFlushConfig(2, 20) +{ +} + +DocumentDBFlushConfig::DocumentDBFlushConfig(uint32_t maxFlushed, uint32_t maxFlushedRetired) + : _maxFlushed(maxFlushed), + _maxFlushedRetired(maxFlushedRetired) +{ +} + +bool +DocumentDBFlushConfig::operator==(const DocumentDBFlushConfig &rhs) const +{ + return _maxFlushed == rhs._maxFlushed && + _maxFlushedRetired == rhs._maxFlushedRetired; +} + +} // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h new file mode 100644 index 00000000000..316d0716477 --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/server/document_db_flush_config.h @@ -0,0 +1,21 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +namespace proton { + +/* + * Flush config, used to adjust flush targets as needed. + */ +class DocumentDBFlushConfig { + uint32_t _maxFlushed; + uint32_t _maxFlushedRetired; + +public: + DocumentDBFlushConfig(); + DocumentDBFlushConfig(uint32_t maxFlushed, uint32_t maxFlushedRetired); + bool operator==(const DocumentDBFlushConfig &rhs) const; + uint32_t getMaxFlushed() const { return _maxFlushed; } + uint32_t getMaxFlushedRetired() const { return _maxFlushedRetired; } +}; + +} // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp index cc491230784..848b1f27574 100644 --- a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp @@ -118,7 +118,8 @@ DocumentDBMaintenanceConfig::DocumentDBMaintenanceConfig() _lidSpaceCompaction(), _attributeUsageFilterConfig(), _attributeUsageSampleInterval(60.0), - _blockableJobConfig() + _blockableJobConfig(), + _flushConfig() { } @@ -131,7 +132,8 @@ DocumentDBMaintenanceConfig(const DocumentDBPruneRemovedDocumentsConfig & const DocumentDBLidSpaceCompactionConfig &lidSpaceCompaction, const AttributeUsageFilterConfig &attributeUsageFilterConfig, double attributeUsageSampleInterval, - const BlockableMaintenanceJobConfig &blockableJobConfig) + const BlockableMaintenanceJobConfig &blockableJobConfig, + const DocumentDBFlushConfig &flushConfig) : _pruneRemovedDocuments(pruneRemovedDocuments), _heartBeat(heartBeat), _sessionCachePruneInterval(groupingSessionPruneInterval), @@ -139,7 +141,8 @@ DocumentDBMaintenanceConfig(const DocumentDBPruneRemovedDocumentsConfig & _lidSpaceCompaction(lidSpaceCompaction), _attributeUsageFilterConfig(attributeUsageFilterConfig), _attributeUsageSampleInterval(attributeUsageSampleInterval), - _blockableJobConfig(blockableJobConfig) + _blockableJobConfig(blockableJobConfig), + _flushConfig(flushConfig) { } @@ -154,7 +157,8 @@ operator==(const DocumentDBMaintenanceConfig &rhs) const _lidSpaceCompaction == rhs._lidSpaceCompaction && _attributeUsageFilterConfig == rhs._attributeUsageFilterConfig && _attributeUsageSampleInterval == rhs._attributeUsageSampleInterval && - _blockableJobConfig == rhs._blockableJobConfig; + _blockableJobConfig == rhs._blockableJobConfig && + _flushConfig == rhs._flushConfig; } } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h index 2d4249531ec..acbbc442c7a 100644 --- a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h +++ b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h @@ -4,6 +4,7 @@ #include <vespa/vespalib/stllike/string.h> #include <vespa/searchcore/proton/attribute/attribute_usage_filter_config.h> #include <vespa/fastos/timestamp.h> +#include "document_db_flush_config.h" namespace proton { @@ -95,6 +96,7 @@ private: AttributeUsageFilterConfig _attributeUsageFilterConfig; double _attributeUsageSampleInterval; BlockableMaintenanceJobConfig _blockableJobConfig; + DocumentDBFlushConfig _flushConfig; public: DocumentDBMaintenanceConfig(); @@ -106,7 +108,8 @@ public: const DocumentDBLidSpaceCompactionConfig &lidSpaceCompaction, const AttributeUsageFilterConfig &attributeUsageFilterConfig, double attributeUsageSampleInterval, - const BlockableMaintenanceJobConfig &blockableJobConfig); + const BlockableMaintenanceJobConfig &blockableJobConfig, + const DocumentDBFlushConfig &flushConfig); bool operator==(const DocumentDBMaintenanceConfig &rhs) const; @@ -133,6 +136,7 @@ public: const BlockableMaintenanceJobConfig &getBlockableJobConfig() const { return _blockableJobConfig; } + const DocumentDBFlushConfig &getFlushConfig() const { return _flushConfig; } }; } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp index 2eee5dff09d..5693e56ccf2 100644 --- a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.cpp @@ -12,7 +12,8 @@ DocumentSubDbInitializerResult::DocumentSubDbInitializerResult() _summaryManager(std::make_shared<SummaryManager::SP>()), _attributeManager(std::make_shared<AttributeManager::SP>()), _indexManager(std::make_shared<IIndexManager::SP>()), - _lidReuseDelayerConfig() + _lidReuseDelayerConfig(), + _flushConfig() { } @@ -23,5 +24,12 @@ setLidReuseDelayerConfig(LidReuseDelayerConfig lidReuseDelayerConfig_in) _lidReuseDelayerConfig = lidReuseDelayerConfig_in; } +void +DocumentSubDbInitializerResult:: +setFlushConfig(const DocumentDBFlushConfig &flushConfig) +{ + _flushConfig = flushConfig; +} + } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h index 67b524332eb..da628c1ab5d 100644 --- a/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h +++ b/searchcore/src/vespa/searchcore/proton/server/document_subdb_initializer_result.h @@ -7,6 +7,7 @@ #include <vespa/searchcore/proton/documentmetastore/document_meta_store_initializer_result.h> #include <vespa/searchcorespi/index/iindexmanager.h> #include <vespa/searchcore/proton/documentmetastore/lid_reuse_delayer_config.h> +#include "document_db_flush_config.h" namespace proton { @@ -24,6 +25,7 @@ private: std::shared_ptr<searchcorespi::IIndexManager::SP> _indexManager; using LidReuseDelayerConfig = documentmetastore::LidReuseDelayerConfig; LidReuseDelayerConfig _lidReuseDelayerConfig; + DocumentDBFlushConfig _flushConfig; public: DocumentSubDbInitializerResult(); @@ -56,6 +58,8 @@ public: const LidReuseDelayerConfig &lidReuseDelayerConfig() const { return _lidReuseDelayerConfig; } + void setFlushConfig(const DocumentDBFlushConfig &flushConfig); + const DocumentDBFlushConfig &getFlushConfig() const { return _flushConfig; } }; } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp index 44f4260f7f8..b83040b04ff 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp @@ -42,7 +42,8 @@ DocumentDBConfig::ComparisonResult::ComparisonResult() schemaChanged(false), maintenanceChanged(false), storeChanged(false), - visibilityDelayChanged(false) + visibilityDelayChanged(false), + flushChanged(false) { } DocumentDBConfig::DocumentDBConfig( @@ -152,6 +153,7 @@ DocumentDBConfig::compare(const DocumentDBConfig &rhs) const retval.maintenanceChanged = !equals<DocumentDBMaintenanceConfig>(_maintenance.get(), rhs._maintenance.get()); retval.storeChanged = (_storeConfig != rhs._storeConfig); retval.visibilityDelayChanged = (_maintenance->getVisibilityDelay() != rhs._maintenance->getVisibilityDelay()); + retval.flushChanged = !equals<DocumentDBMaintenanceConfig>(_maintenance.get(), rhs._maintenance.get(), [](const auto &l, const auto &r) { return l.getFlushConfig() == r.getFlushConfig(); }); return retval; } diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h index 4250ce61175..cfca754a3d6 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h @@ -46,6 +46,7 @@ public: bool maintenanceChanged; bool storeChanged; bool visibilityDelayChanged; + bool flushChanged; ComparisonResult(); ComparisonResult &setRankProfilesChanged(bool val) { rankProfilesChanged = val; return *this; } @@ -63,7 +64,20 @@ public: ComparisonResult &setMaintenanceChanged(bool val) { maintenanceChanged = val; return *this; } ComparisonResult &setStoreChanged(bool val) { storeChanged = val; return *this; } - ComparisonResult &setVisibilityDelayChanged(bool val) { visibilityDelayChanged = val; return *this; } + ComparisonResult &setVisibilityDelayChanged(bool val) { + visibilityDelayChanged = val; + if (val) { + maintenanceChanged = true; + } + return *this; + } + ComparisonResult &setFlushChanged(bool val) { + flushChanged = val; + if (val) { + maintenanceChanged = true; + } + return *this; + } }; using SP = std::shared_ptr<DocumentDBConfig>; @@ -117,6 +131,14 @@ private: } return rhs != NULL && *lhs == *rhs; } + template <typename T, typename Func> + bool equals(const T *lhs, const T *rhs, Func isEqual) const + { + if (lhs == NULL) { + return rhs == NULL; + } + return rhs != NULL && isEqual(*lhs, *rhs); + } public: DocumentDBConfig(int64_t generation, const RankProfilesConfigSP &rankProfiles, diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp index 6aea5234fbe..b9f59c4c411 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp @@ -136,7 +136,10 @@ buildMaintenanceConfig(const BootstrapConfig::SP &bootstrapConfig, proton.writefilter.sampleinterval, BlockableMaintenanceJobConfig( proton.maintenancejobs.resourcelimitfactor, - proton.maintenancejobs.maxoutstandingmoveops)); + proton.maintenancejobs.maxoutstandingmoveops), + DocumentDBFlushConfig( + proton.index.maxflushed, + proton.index.maxflushedretired)); } template<typename T> diff --git a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp index 0cea30385c3..d06319ae7f9 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.cpp @@ -72,7 +72,7 @@ CachedSelect::SP DocumentRetrieverBase::parseSelect(const vespalib::string &selection) const { { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); if (_selectCache.hasKey(selection)) return _selectCache[selection]; } @@ -86,7 +86,7 @@ DocumentRetrieverBase::parseSelect(const vespalib::string &selection) const getAttrMgr(), _hasFields); - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); if (_selectCache.hasKey(selection)) return _selectCache[selection]; _selectCache.insert(selection, nselect); diff --git a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h index 3a0752e4c95..53341e0d335 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h +++ b/searchcore/src/vespa/searchcore/proton/server/documentretrieverbase.h @@ -7,7 +7,7 @@ #include <vespa/document/fieldvalue/document.h> #include <vespa/vespalib/stllike/lrucache_map.h> #include <vespa/searchlib/attribute/iattributemanager.h> -#include <vespa/vespalib/util/sync.h> +#include <mutex> namespace proton { @@ -20,7 +20,7 @@ class DocumentRetrieverBase : public IDocumentRetriever using SelectCache = vespalib::lrucache_map<vespalib::LruParam<vespalib::string, CachedSelect::SP>>; mutable SelectCache _selectCache; - vespalib::Lock _lock; + mutable std::mutex _lock; document::Document::UP _emptyDoc; const bool _hasFields; diff --git a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp index c766cc89bb3..4c1dd47569b 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.cpp @@ -309,6 +309,15 @@ DocumentSubDBCollection::close() } void +DocumentSubDBCollection::setBucketStateCalculator(const IBucketStateCalculatorSP &calc) +{ + _calc = calc; + for (auto subDb : _subDBs) { + subDb->setBucketStateCalculator(calc); + } +} + +void DocumentSubDBCollection::tearDownReferences(IDocumentDBReferenceResolver &resolver) { for (auto subDb : _subDBs) { diff --git a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h index 7290250c59e..ea07e391f69 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h +++ b/searchcore/src/vespa/searchcore/proton/server/documentsubdbcollection.h @@ -94,9 +94,7 @@ public: const HwInfo &hwInfo); ~DocumentSubDBCollection(); - void setBucketStateCalculator(const IBucketStateCalculatorSP &calc) { - _calc = calc; - } + void setBucketStateCalculator(const IBucketStateCalculatorSP &calc); void createRetrievers(); void maintenanceSync(MaintenanceController &mc, ICommitable &commit); diff --git a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp index 8091196cc4b..a5523083df7 100644 --- a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp @@ -2,7 +2,6 @@ #include "executorthreadingservice.h" #include <vespa/vespalib/util/executor.h> -#include <vespa/vespalib/util/sync.h> using vespalib::ThreadStackExecutorBase; diff --git a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp index bc1715e3c04..c9b3df5a6ed 100644 --- a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp @@ -11,6 +11,7 @@ #include <vespa/searchcore/proton/bucketdb/ibucketdbhandler.h> #include <vespa/searchcore/proton/persistenceengine/i_resource_write_filter.h> #include <vespa/searchcore/proton/persistenceengine/transport_latch.h> +#include <vespa/searchcore/proton/common/eventlogger.h> #include <vespa/searchcorespi/index/ithreadingservice.h> #include <vespa/searchlib/common/gatecallback.h> #include <vespa/vespalib/util/exceptions.h> @@ -32,8 +33,6 @@ using vespalib::Executor; using vespalib::IllegalStateException; using vespalib::makeLambdaTask; using vespalib::make_string; -using vespalib::MonitorGuard; -using vespalib::LockGuard; using std::make_unique; using std::make_shared; @@ -80,7 +79,7 @@ void FeedHandler::doHandleOperation(FeedToken token, FeedOperation::UP op) { assert(_writeService.master().isCurrentThread()); - LockGuard guard(_feedLock); + std::lock_guard<std::mutex> guard(_feedLock); _feedState->handleOperation(std::move(token), std::move(op)); } @@ -261,6 +260,7 @@ FeedHandler::performPrune(SerialNum flushedSerial) tlsPrune(flushedSerial); // throws on error LOG(debug, "Pruned TLS to token %" PRIu64 ".", flushedSerial); _owner.onPerformPrune(flushedSerial); + EventLogger::transactionLogPruneComplete(_tlsMgr.getDomainName(), flushedSerial); } catch (const IllegalStateException & e) { LOG(warning, "FeedHandler::performPrune failed due to '%s'.", e.what()); } @@ -282,7 +282,7 @@ FeedHandler::getFeedState() const { FeedState::SP state; { - LockGuard guard(_feedLock); + std::lock_guard<std::mutex> guard(_feedLock); state = _feedState; } return state; @@ -292,13 +292,13 @@ FeedHandler::getFeedState() const void FeedHandler::changeFeedState(FeedState::SP newState) { - LockGuard guard(_feedLock); + std::lock_guard<std::mutex> guard(_feedLock); changeFeedState(std::move(newState), guard); } void -FeedHandler::changeFeedState(FeedState::SP newState, const LockGuard &) +FeedHandler::changeFeedState(FeedState::SP newState, const std::lock_guard<std::mutex> &) { LOG(debug, "Change feed state from '%s' -> '%s'", _feedState->getName().c_str(), newState->getName().c_str()); _feedState = newState; @@ -390,8 +390,9 @@ FeedHandler::replayTransactionLog(SerialNum flushedIndexMgrSerial, SerialNum flu void FeedHandler::flushDone(SerialNum flushedSerial) { - // Called by flush worker thread after performing a flush task + // Called by flush scheduler thread after flush worker thread has completed a flush task _writeService.master().execute(makeLambdaTask([this, flushedSerial]() { performFlushDone(flushedSerial); })); + _writeService.master().sync(); } @@ -588,7 +589,7 @@ void FeedHandler::syncTls(SerialNum syncTo) { { - LockGuard guard(_syncLock); + std::lock_guard<std::mutex> guard(_syncLock); if (_syncedSerialNum >= syncTo) return; } @@ -597,7 +598,7 @@ FeedHandler::syncTls(SerialNum syncTo) } SerialNum syncedTo(_tlsWriter.sync(syncTo)); { - LockGuard guard(_syncLock); + std::lock_guard<std::mutex> guard(_syncLock); if (_syncedSerialNum < syncedTo) _syncedSerialNum = syncedTo; } diff --git a/searchcore/src/vespa/searchcore/proton/server/feedhandler.h b/searchcore/src/vespa/searchcore/proton/server/feedhandler.h index 8c28fcdc1ea..171462a6c13 100644 --- a/searchcore/src/vespa/searchcore/proton/server/feedhandler.h +++ b/searchcore/src/vespa/searchcore/proton/server/feedhandler.h @@ -13,6 +13,7 @@ #include <vespa/searchcore/proton/common/doctypename.h> #include <vespa/searchcore/proton/common/feedtoken.h> #include <vespa/searchlib/transactionlog/translogclient.h> +#include <mutex> namespace searchcorespi { namespace index { class IThreadingService; } } @@ -85,12 +86,12 @@ private: SerialNum _serialNum; SerialNum _prunedSerialNum; bool _delayedPrune; - vespalib::Lock _feedLock; + mutable std::mutex _feedLock; FeedStateSP _feedState; // used by master write thread tasks IFeedView *_activeFeedView; bucketdb::IBucketDBHandler *_bucketDBHandler; - vespalib::Lock _syncLock; + std::mutex _syncLock; SerialNum _syncedSerialNum; bool _allowSync; // Sanity check @@ -129,7 +130,7 @@ private: FeedStateSP getFeedState() const; void changeFeedState(FeedStateSP newState); - void changeFeedState(FeedStateSP newState, const vespalib::LockGuard &feedGuard); + void changeFeedState(FeedStateSP newState, const std::lock_guard<std::mutex> &feedGuard); public: FeedHandler(const FeedHandler &) = delete; FeedHandler & operator = (const FeedHandler &) = delete; diff --git a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp index c021d786216..d5deb4482c4 100644 --- a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.cpp @@ -9,13 +9,13 @@ using document::BucketId; using vespalib::makeClosure; using vespalib::makeTask; -using vespalib::MonitorGuard; namespace proton { -FrozenBucketsMap::FrozenBucketsMap() : - _lock(), - _map() +FrozenBucketsMap::FrozenBucketsMap() + : _lock(), + _cond(), + _map() { } FrozenBucketsMap::~FrozenBucketsMap() { @@ -25,12 +25,12 @@ FrozenBucketsMap::~FrozenBucketsMap() { void FrozenBucketsMap::freezeBucket(BucketId bucket) { - MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); std::pair<BucketId, FrozenBucket> tryVal(std::make_pair(bucket, FrozenBucket(FrozenBucket::Reader))); std::pair<Map::iterator, bool> res; for (res = _map.insert(tryVal); !res.second && (res.first->second.isExclusive()); res = _map.insert(tryVal)) { - guard.wait(); + _cond.wait(guard); } if (!res.second) { @@ -42,7 +42,7 @@ FrozenBucketsMap::freezeBucket(BucketId bucket) { bool FrozenBucketsMap::thawBucket(BucketId bucket) { - MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); Map::iterator it(_map.find(bucket)); assert(it != _map.end()); assert(it->second.hasReaders()); @@ -52,7 +52,7 @@ FrozenBucketsMap::thawBucket(BucketId bucket) isLastAndContended = true; } _map.erase(it); - guard.broadcast(); + _cond.notify_all(); } else { it->second.removeReader(); } @@ -63,7 +63,7 @@ FrozenBucketsMap::thawBucket(BucketId bucket) IFrozenBucketHandler::ExclusiveBucketGuard::UP FrozenBucketsMap::acquireExclusiveBucket(document::BucketId bucket) { - MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); Map::iterator it(_map.find(bucket)); if (it != _map.end()) { assert(it->second.hasReaders()); @@ -77,11 +77,11 @@ FrozenBucketsMap::acquireExclusiveBucket(document::BucketId bucket) void FrozenBucketsMap::releaseExclusiveBucket(document::BucketId bucket) { - MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); Map::const_iterator it(_map.find(bucket)); assert ((it != _map.end()) && (it->second.isExclusive())); _map.erase(it); - guard.broadcast(); + _cond.notify_all(); } FrozenBuckets::FrozenBuckets(IThreadService &masterThread) : diff --git a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h index 9f389b89adf..be4d28f1e9e 100644 --- a/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h +++ b/searchcore/src/vespa/searchcore/proton/server/frozenbuckets.h @@ -4,7 +4,9 @@ #include "ifrozenbuckethandler.h" #include "ibucketfreezer.h" -#include <vespa/vespalib/util/sync.h> +#include <mutex> +#include <condition_variable> +#include <cassert> #include <map> #include <vector> @@ -66,8 +68,9 @@ private: bool _notifyWriter; }; typedef std::map<document::BucketId, FrozenBucket> Map; - vespalib::Monitor _lock; - Map _map; + std::mutex _lock; + std::condition_variable _cond; + Map _map; }; /** diff --git a/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h b/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h index 2251a24c58a..d41c2088518 100644 --- a/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h +++ b/searchcore/src/vespa/searchcore/proton/server/idocumentsubdb.h @@ -25,6 +25,7 @@ class DocumentSubDbInitializerResult; class FeedHandler; class FileConfigManager; class IAttributeManager; +class IBucketStateCalculator; class IDcoumentRetriever; class IDocumentDBReferenceResolver; class IDocumentDBReference; @@ -72,6 +73,7 @@ public: virtual IReprocessingTask::List applyConfig(const DocumentDBConfig &newConfigSnapshot, const DocumentDBConfig &oldConfigSnapshot, SerialNum serialNum, const ReconfigParams ¶ms, IDocumentDBReferenceResolver &resolver) = 0; + virtual void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) = 0; virtual std::shared_ptr<ISearchHandler> getSearchView() const = 0; virtual std::shared_ptr<IFeedView> getFeedView() const = 0; diff --git a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp index 2ae8d826ebc..fad00fa00e6 100644 --- a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp @@ -6,7 +6,7 @@ #include "lid_space_compaction_job.h" #include <vespa/searchcore/proton/common/eventlogger.h> #include <vespa/searchlib/common/gatecallback.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <cassert> #include <vespa/log/log.h> diff --git a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp index a5b0df67b77..35d6973e34b 100644 --- a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.cpp @@ -20,7 +20,7 @@ shouldUseConservativeMode(const ResourceUsageState &resourceState, } void -MemoryFlushConfigUpdater::considerUseConservativeDiskMode(const LockGuard &, +MemoryFlushConfigUpdater::considerUseConservativeDiskMode(const LockGuard &guard, MemoryFlush::Config &newConfig) { if (shouldUseConservativeMode(_currState.diskState(), _useConservativeDiskMode, @@ -30,6 +30,9 @@ MemoryFlushConfigUpdater::considerUseConservativeDiskMode(const LockGuard &, _useConservativeDiskMode = true; } else { _useConservativeDiskMode = false; + if (_nodeRetired) { + considerUseRelaxedDiskMode(guard, newConfig); + } } } @@ -49,6 +52,18 @@ MemoryFlushConfigUpdater::considerUseConservativeMemoryMode(const LockGuard &, } void +MemoryFlushConfigUpdater::considerUseRelaxedDiskMode(const LockGuard &, MemoryFlush::Config &newConfig) +{ + double utilization = _currState.diskState().utilization(); + double bloatMargin = _currConfig.conservative.lowwatermarkfactor - utilization; + if (bloatMargin > 0.0) { + // Node retired and disk utiliation is below low mater mark factor. + newConfig.diskBloatFactor = 1.0; + newConfig.globalDiskBloatFactor = std::max(bloatMargin * 0.8, _currConfig.diskbloatfactor); + } +} + +void MemoryFlushConfigUpdater::updateFlushStrategy(const LockGuard &guard) { MemoryFlush::Config newConfig = convertConfig(_currConfig, _memory); @@ -66,7 +81,8 @@ MemoryFlushConfigUpdater::MemoryFlushConfigUpdater(const MemoryFlush::SP &flushS _memory(memory), _currState(), _useConservativeDiskMode(false), - _useConservativeMemoryMode(false) + _useConservativeMemoryMode(false), + _nodeRetired(false) { } @@ -86,6 +102,14 @@ MemoryFlushConfigUpdater::notifyDiskMemUsage(DiskMemUsageState newState) updateFlushStrategy(guard); } +void +MemoryFlushConfigUpdater::setNodeRetired(bool nodeRetired) +{ + LockGuard guard(_mutex); + _nodeRetired = nodeRetired; + updateFlushStrategy(guard); +} + namespace { size_t diff --git a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h index e6d8f7610f8..28ee330689d 100644 --- a/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h +++ b/searchcore/src/vespa/searchcore/proton/server/memory_flush_config_updater.h @@ -28,12 +28,15 @@ private: DiskMemUsageState _currState; bool _useConservativeDiskMode; bool _useConservativeMemoryMode; + bool _nodeRetired; void considerUseConservativeDiskMode(const LockGuard &guard, MemoryFlush::Config &newConfig); void considerUseConservativeMemoryMode(const LockGuard &guard, MemoryFlush::Config &newConfig); + void considerUseRelaxedDiskMode(const LockGuard &guard, + MemoryFlush::Config &newConfig); void updateFlushStrategy(const LockGuard &guard); public: @@ -43,6 +46,7 @@ public: const ProtonConfig::Flush::Memory &config, const HwInfo::Memory &memory); void setConfig(const ProtonConfig::Flush::Memory &newConfig); + void setNodeRetired(bool nodeRetired); virtual void notifyDiskMemUsage(DiskMemUsageState newState) override; static MemoryFlush::Config convertConfig(const ProtonConfig::Flush::Memory &config, diff --git a/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp b/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp index e80f2645fcf..7d97cc4030a 100644 --- a/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/memoryflush.cpp @@ -90,14 +90,14 @@ MemoryFlush::~MemoryFlush() { } MemoryFlush::Config MemoryFlush::getConfig() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _config; } void MemoryFlush::setConfig(const Config &config) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _config = config; } @@ -120,13 +120,6 @@ size_t computeGain(const IFlushTarget::DiskGain & gain) { return std::max(100000000l, std::max(gain.getBefore(), gain.getAfter())); } -bool isDiskBloatToHigh(const IFlushTarget::DiskGain & totalDisk, - const MemoryFlush::Config & config, - const IFlushTarget::DiskGain & dgain) -{ - return (totalDisk.gain() > config.globalDiskBloatFactor * computeGain(totalDisk)) - || (dgain.gain() > config.diskBloatFactor * computeGain(dgain)); -} } @@ -167,9 +160,9 @@ MemoryFlush::getFlushTargets(const FlushContext::List &targetList, order = TLSSIZE; } } - if (((totalMemory >= config.maxGlobalMemory) || (mgain >= config.maxMemoryGain)) && (order < MEMORY)) { + if ((mgain >= config.maxMemoryGain) && (order < MEMORY)) { order = MEMORY; - } else if (isDiskBloatToHigh(totalDisk, config, dgain) && (order < DISKBLOAT)) { + } else if ((dgain.gain() > config.diskBloatFactor * computeGain(dgain)) && (order < DISKBLOAT)) { order = DISKBLOAT; } else if ((timeDiff >= config.maxTimeGain) && (order < MAXAGE)) { order = MAXAGE; @@ -195,6 +188,14 @@ MemoryFlush::getFlushTargets(const FlushContext::List &targetList, timeDiff.sec(), getOrderName(order).c_str()); } + if (!targetList.empty()) { + if ((totalMemory >= config.maxGlobalMemory) && (order < MEMORY)) { + order = MEMORY; + } + if ((totalDisk.gain() > config.globalDiskBloatFactor * computeGain(totalDisk)) && (order < DISKBLOAT)) { + order = DISKBLOAT; + } + } FlushContext::List fv(targetList); std::sort(fv.begin(), fv.end(), CompareTarget(order, tlsStatsMap)); // No desired order and no urgent needs; no flush required at this moment. diff --git a/searchcore/src/vespa/searchcore/proton/server/memoryflush.h b/searchcore/src/vespa/searchcore/proton/server/memoryflush.h index 552a406aad9..7e9bba59b25 100644 --- a/searchcore/src/vespa/searchcore/proton/server/memoryflush.h +++ b/searchcore/src/vespa/searchcore/proton/server/memoryflush.h @@ -2,7 +2,7 @@ #pragma once #include <vespa/searchcore/proton/flushengine/iflushstrategy.h> -#include <vespa/vespalib/util/sync.h> +#include <mutex> namespace proton { @@ -39,7 +39,7 @@ public: private: /// Needed as flushDone is called in different context from the rest - vespalib::Lock _lock; + mutable std::mutex _lock; Config _config; /// The time when the strategy was started. fastos::TimeStamp _startTime; diff --git a/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h b/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h index 118f9601abd..6224b3b693a 100644 --- a/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h +++ b/searchcore/src/vespa/searchcore/proton/server/packetwrapper.h @@ -4,7 +4,7 @@ #include "tls_replay_progress.h" #include <vespa/searchlib/transactionlog/common.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> namespace proton { /** diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.cpp b/searchcore/src/vespa/searchcore/proton/server/proton.cpp index 6de5410470f..8ce7e0c79fe 100644 --- a/searchcore/src/vespa/searchcore/proton/server/proton.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/proton.cpp @@ -36,7 +36,6 @@ LOG_SETUP(".proton.server.proton"); using document::DocumentTypeRepo; using vespalib::FileHeader; using vespalib::IllegalStateException; -using vespalib::MonitorGuard; using vespalib::Slime; using vespalib::slime::ArrayInserter; using vespalib::slime::Cursor; @@ -195,7 +194,6 @@ Proton::Proton(const config::ConfigUri & configUri, _queryLimiter(), _clock(0.010), _threadPool(128 * 1024), - _configGenMonitor(), _configGen(0), _distributionKey(-1), _isInitializing(true), @@ -704,8 +702,11 @@ Proton::updateMetrics(const vespalib::MonitorGuard &) ContentProtonMetrics &metrics = _metricsEngine->root(); metrics.transactionLog.update(_tls->getTransLogServer()->getDomainStats()); const DiskMemUsageFilter &usageFilter = _diskMemUsageSampler->writeFilter(); - metrics.resourceUsage.disk.set(usageFilter.getDiskUsedRatio()); - metrics.resourceUsage.memory.set(usageFilter.getMemoryUsedRatio()); + DiskMemUsageState usageState = usageFilter.usageState(); + metrics.resourceUsage.disk.set(usageState.diskState().usage()); + metrics.resourceUsage.diskUtilization.set(usageState.diskState().utilization()); + metrics.resourceUsage.memory.set(usageState.memoryState().usage()); + metrics.resourceUsage.memoryUtilization.set(usageState.memoryState().utilization()); metrics.resourceUsage.memoryMappings.set(usageFilter.getMemoryStats().getMappingsCount()); metrics.resourceUsage.openFileDescriptors.set(countOpenFiles()); metrics.resourceUsage.feedingBlocked.set((usageFilter.acceptWriteOperation() ? 0.0 : 1.0)); @@ -774,7 +775,11 @@ Proton::setClusterState(const storage::spi::ClusterState &calc) // about whether node is supposed to be up or not. Match engine // needs to know this in order to stop serving queries. bool nodeUp(calc.nodeUp()); + bool nodeRetired(calc.nodeRetired()); _matchEngine->setNodeUp(nodeUp); + if (_memoryFlushConfigUpdater) { + _memoryFlushConfigUpdater->setNodeRetired(nodeRetired); + } } namespace { diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.h b/searchcore/src/vespa/searchcore/proton/server/proton.h index 532c6651cfa..8d1026340ca 100644 --- a/searchcore/src/vespa/searchcore/proton/server/proton.h +++ b/searchcore/src/vespa/searchcore/proton/server/proton.h @@ -116,7 +116,6 @@ private: matching::QueryLimiter _queryLimiter; vespalib::Clock _clock; FastOS_ThreadPool _threadPool; - vespalib::Monitor _configGenMonitor; int64_t _configGen; uint32_t _distributionKey; bool _isInitializing; diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp index 710b0ad228c..adb52583a58 100644 --- a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.cpp @@ -24,7 +24,9 @@ ProtonConfigFetcher::ProtonConfigFetcher(const config::ConfigUri & configUri, IP _owner(owner), _mutex(), _dbManagerMap(), - _threadPool(128 * 1024, 1) + _threadPool(128 * 1024, 1), + _oldDocumentTypeRepos(), + _currentDocumentTypeRepo() { } @@ -100,10 +102,11 @@ ProtonConfigFetcher::reconfigure() assert(insres.first->second->getGeneration() == generation); } } - auto configSnapshot = std::make_shared<ProtonConfigSnapshot>(std::move(bootstrapConfig), std::move(dbConfigs)); + auto configSnapshot = std::make_shared<ProtonConfigSnapshot>(bootstrapConfig, std::move(dbConfigs)); LOG(debug, "Reconfiguring proton with gen %" PRId64, generation); _owner.reconfigure(std::move(configSnapshot)); LOG(debug, "Reconfigured proton with gen %" PRId64, generation); + rememberDocumentTypeRepo(bootstrapConfig->getDocumentTypeRepoSP()); } void @@ -189,4 +192,22 @@ ProtonConfigFetcher::getDocumentDBConfig(const DocTypeName & docTypeName) const return it->second->getConfig(); } +void +ProtonConfigFetcher::rememberDocumentTypeRepo(std::shared_ptr<document::DocumentTypeRepo> repo) +{ + // Ensure that previous document type repo is kept alive, and also + // any document type repo that was current within last 10 minutes. + using namespace std::chrono_literals; + if (repo == _currentDocumentTypeRepo) { + return; // no change + } + auto &repos = _oldDocumentTypeRepos; + TimePoint now = Clock::now(); + while (!repos.empty() && repos.front().first < now) { + repos.pop_front(); + } + repos.emplace_back(now + 10min, _currentDocumentTypeRepo); + _currentDocumentTypeRepo = repo; +} + } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h index 9adbabbc174..fa1f75ebe91 100644 --- a/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h +++ b/searchcore/src/vespa/searchcore/proton/server/proton_config_fetcher.h @@ -9,6 +9,9 @@ #include "bootstrapconfigmanager.h" #include "documentdbconfigmanager.h" #include "i_document_db_config_owner.h" +#include <chrono> + +namespace document { class DocumentTypeRepo; } namespace proton { @@ -47,6 +50,9 @@ public: private: typedef std::map<DocTypeName, DocumentDBConfigManager::SP> DBManagerMap; + using Clock = std::chrono::steady_clock; + using TimePoint = std::chrono::time_point<Clock>; + using OldDocumentTypeRepo = std::pair<TimePoint, std::shared_ptr<document::DocumentTypeRepo>>; BootstrapConfigManager _bootstrapConfigManager; config::ConfigRetriever _retriever; @@ -57,11 +63,14 @@ private: DBManagerMap _dbManagerMap; FastOS_ThreadPool _threadPool; + std::deque<OldDocumentTypeRepo> _oldDocumentTypeRepos; + std::shared_ptr<document::DocumentTypeRepo> _currentDocumentTypeRepo; void fetchConfigs(); void updateDocumentDBConfigs(const BootstrapConfigSP & config, const config::ConfigSnapshot & snapshot); void reconfigure(); const config::ConfigKeySet pruneManagerMap(const BootstrapConfigSP & config); + void rememberDocumentTypeRepo(std::shared_ptr<document::DocumentTypeRepo> repo); }; diff --git a/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp b/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp index 377b012de99..8ec41ae3e3c 100644 --- a/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp @@ -64,7 +64,7 @@ bool ReconfigParams::shouldSubDbsChange() const { return shouldMatchersChange() || shouldAttributeManagerChange() || shouldSummaryManagerChange() - || _res.documentTypeRepoChanged || _res.documenttypesChanged || _res.storeChanged; + || _res.documentTypeRepoChanged || _res.documenttypesChanged || _res.storeChanged || _res.flushChanged; } bool diff --git a/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp b/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp index 194e190bb7b..3d770fad313 100644 --- a/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/removedonecontext.cpp @@ -3,6 +3,7 @@ #include "removedonecontext.h" #include "removedonetask.h" #include <vespa/searchcore/proton/reference/i_gid_to_lid_change_handler.h> +#include <cassert> namespace proton { diff --git a/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp b/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp index 33b37649ed2..6f1fd8b90e0 100644 --- a/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/resource_usage_explorer.cpp @@ -33,21 +33,23 @@ void ResourceUsageExplorer::get_state(const vespalib::slime::Inserter &inserter, bool full) const { Cursor &object = inserter.insertObject(); + DiskMemUsageState usageState = _usageFilter.usageState(); if (full) { - DiskMemUsageFilter::Config config = _usageFilter.getConfig(); Cursor &disk = object.setObject("disk"); - disk.setDouble("usedRatio", _usageFilter.getDiskUsedRatio()); - disk.setDouble("usedLimit", config._diskLimit); + disk.setDouble("usage", usageState.diskState().usage()); + disk.setDouble("limit", usageState.diskState().limit()); + disk.setDouble("utilization", usageState.diskState().utilization()); convertDiskStatsToSlime(_usageFilter.getHwInfo(), _usageFilter.getDiskUsedSize(), disk.setObject("stats")); Cursor &memory = object.setObject("memory"); - memory.setDouble("usedRatio", _usageFilter.getMemoryUsedRatio()); - memory.setDouble("usedLimit", config._memoryLimit); + memory.setDouble("usage", usageState.memoryState().usage()); + memory.setDouble("limit", usageState.memoryState().limit()); + memory.setDouble("utilization", usageState.memoryState().utilization()); memory.setLong("physicalMemory", _usageFilter.getHwInfo().memory().sizeBytes()); convertMemoryStatsToSlime(_usageFilter.getMemoryStats(), memory.setObject("stats")); } else { - object.setDouble("disk", _usageFilter.getDiskUsedRatio()); - object.setDouble("memory", _usageFilter.getMemoryUsedRatio()); + object.setDouble("disk", usageState.diskState().usage()); + object.setDouble("memory", usageState.memoryState().usage()); } } diff --git a/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h b/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h index 7ac127c6b25..29f9959aea9 100644 --- a/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h +++ b/searchcore/src/vespa/searchcore/proton/server/resource_usage_state.h @@ -5,7 +5,10 @@ namespace proton { /** - * Class representing the state of an resource (e.g. disk or memory) with its limit and current usage. + * Class representing the state of an resource (e.g. disk or memory) with its limit and current usage: + * - usage: How much of this resource is currently used (number between 0 and 1). + * - limit: How much of this resource is allowed to use (number between 0 and 1). + * - utilization: How much of the allowed part of this resource is used (usage/limit). */ class ResourceUsageState { @@ -33,6 +36,7 @@ public: } double limit() const { return _limit; } double usage() const { return _usage; } + double utilization() const { return _usage/_limit; } bool aboveLimit() const { return aboveLimit(1.0); } diff --git a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp index 7f6fdf0e06f..913efa469d3 100644 --- a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.cpp @@ -4,6 +4,7 @@ #include "proton.h" #include <vespa/vespalib/util/closuretask.h> #include <vespa/fnet/frt/supervisor.h> +#include <chrono> #include <vespa/log/log.h> LOG_SETUP(".proton.server.rtchooks"); @@ -35,8 +36,8 @@ RPCHooksBase::checkState(StateArg::UP arg) { fastos::TimeStamp now(fastos::ClockSystem::now()); if (now < arg->_dueTime) { - MonitorGuard guard(_stateMonitor); - if ( guard.wait(std::min(1000L, (arg->_dueTime - now)/fastos::TimeStamp::MS)) ) { + std::unique_lock<std::mutex> guard(_stateLock); + if (_stateCond.wait_for(guard, std::chrono::milliseconds(std::min(1000L, (arg->_dueTime - now)/fastos::TimeStamp::MS))) == std::cv_status::no_timeout) { LOG(debug, "state has changed"); reportState(*arg->_session, arg->_req); arg->_req->Return(); @@ -203,6 +204,8 @@ RPCHooksBase::RPCHooksBase(Params ¶ms) _docsumByRPC(new DocsumByRPC(_proton.getDocsumBySlime())), _orb(std::make_unique<FRT_Supervisor>()), _regAPI(*_orb, params.slobrok_config), + _stateLock(), + _stateCond(), _executor(48, 128 * 1024) { } @@ -225,8 +228,8 @@ RPCHooksBase::close() _orb->ShutDown(true); _executor.shutdown(); { - MonitorGuard guard(_stateMonitor); - guard.broadcast(); + std::lock_guard<std::mutex> guard(_stateLock); + _stateCond.notify_all(); } _executor.sync(); } diff --git a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h index ab5980a39fa..994699ac480 100644 --- a/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h +++ b/searchcore/src/vespa/searchcore/proton/server/rpc_hooks.h @@ -8,6 +8,8 @@ #include <vespa/vespalib/stllike/string.h> #include <vespa/vespalib/util/threadstackexecutor.h> #include <vespa/searchlib/common/packets.h> +#include <mutex> +#include <condition_variable> namespace proton { @@ -61,7 +63,8 @@ private: std::unique_ptr<DocsumByRPC> _docsumByRPC; std::unique_ptr<FRT_Supervisor> _orb; slobrok::api::RegisterAPI _regAPI; - vespalib::Monitor _stateMonitor; + std::mutex _stateLock; + std::condition_variable _stateCond; vespalib::ThreadStackExecutor _executor; void initRPC(); diff --git a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp index 4069a871210..e05ab4b798f 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp @@ -5,6 +5,7 @@ #include "document_subdb_initializer.h" #include "reconfig_params.h" #include "i_document_subdb_owner.h" +#include "ibucketstatecalculator.h" #include <vespa/searchcore/proton/attribute/attribute_writer.h> #include <vespa/searchcore/proton/flushengine/threadedflushtarget.h> #include <vespa/searchcore/proton/index/index_manager_initializer.h> @@ -49,7 +50,9 @@ SearchableDocSubDB::SearchableDocSubDB(const Config &cfg, const Context &ctx) getSubDbName(), ctx._fastUpdCtx._storeOnlyCtx._owner.getDistributionKey()), _numSearcherThreads(cfg._numSearcherThreads), _warmupExecutor(ctx._warmupExecutor), - _realGidToLidChangeHandler(std::make_shared<GidToLidChangeHandler>()) + _realGidToLidChangeHandler(std::make_shared<GidToLidChangeHandler>()), + _flushConfig(), + _nodeRetired(false) { _gidToLidChangeHandler = _realGidToLidChangeHandler; } @@ -136,6 +139,7 @@ SearchableDocSubDB::setup(const DocumentSubDbInitializerResult &initResult) Parent::setup(initResult); setupIndexManager(initResult.indexManager()); _docIdLimit.set(_dms->getCommittedDocIdLimit()); + applyFlushConfig(initResult.getFlushConfig()); } void @@ -160,6 +164,7 @@ SearchableDocSubDB::applyConfig(const DocumentDBConfig &newConfigSnapshot, const StoreOnlyDocSubDB::reconfigure(newConfigSnapshot.getStoreConfig()); IReprocessingTask::List tasks; updateLidReuseDelayer(&newConfigSnapshot); + applyFlushConfig(newConfigSnapshot.getMaintenanceConfigSP()->getFlushConfig()); if (params.shouldMatchersChange() && _addMetrics) { reconfigureMatchingMetrics(newConfigSnapshot.getRankProfilesConfig()); } @@ -185,6 +190,27 @@ SearchableDocSubDB::applyConfig(const DocumentDBConfig &newConfigSnapshot, const } void +SearchableDocSubDB::applyFlushConfig(const DocumentDBFlushConfig &flushConfig) +{ + _flushConfig = flushConfig; + propagateFlushConfig(); +} + +void +SearchableDocSubDB::propagateFlushConfig() +{ + uint32_t maxFlushed = _nodeRetired ? _flushConfig.getMaxFlushedRetired() : _flushConfig.getMaxFlushed(); + _indexMgr->setMaxFlushed(maxFlushed); +} + +void +SearchableDocSubDB::setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) +{ + _nodeRetired = calc->nodeRetired(); + propagateFlushConfig(); +} + +void SearchableDocSubDB::initViews(const DocumentDBConfig &configSnapshot, const SessionManager::SP &sessionManager) { assert(_writeService.master().isCurrentThread()); diff --git a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h index 6da1a337cda..0852e028c25 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h +++ b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.h @@ -85,6 +85,8 @@ private: const size_t _numSearcherThreads; vespalib::ThreadExecutor &_warmupExecutor; std::shared_ptr<GidToLidChangeHandler> _realGidToLidChangeHandler; + DocumentDBFlushConfig _flushConfig; + bool _nodeRetired; // Note: lifetime of indexManager must be handled by caller. std::shared_ptr<initializer::InitializerTask> @@ -100,6 +102,8 @@ private: bool reconfigure(vespalib::Closure0<bool>::UP closure) override; void reconfigureIndexSearchable(); void syncViews(); + void applyFlushConfig(const DocumentDBFlushConfig &flushConfig); + void propagateFlushConfig(); protected: IFlushTargetList getFlushTargetsInternal() override; @@ -120,6 +124,7 @@ public: IReprocessingTask::List applyConfig(const DocumentDBConfig &newConfigSnapshot, const DocumentDBConfig &oldConfigSnapshot, SerialNum serialNum, const ReconfigParams ¶ms, IDocumentDBReferenceResolver &resolver) override; + virtual void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) override; void clearViews() override; diff --git a/searchcore/src/vespa/searchcore/proton/server/simpleflush.h b/searchcore/src/vespa/searchcore/proton/server/simpleflush.h index 71dd629bdd1..c1f6e689eaa 100644 --- a/searchcore/src/vespa/searchcore/proton/server/simpleflush.h +++ b/searchcore/src/vespa/searchcore/proton/server/simpleflush.h @@ -2,7 +2,6 @@ #pragma once #include <vespa/searchcore/proton/flushengine/iflushstrategy.h> -#include <vespa/vespalib/util/sync.h> namespace proton { diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp index e78f8136d26..c5a8706a0ce 100644 --- a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp @@ -329,6 +329,7 @@ StoreOnlyDocSubDB::createInitializer(const DocumentDBConfig &configSnapshot, Ser LidReuseDelayerConfig lidReuseDelayerConfig(configSnapshot); result->writableResult().setLidReuseDelayerConfig(lidReuseDelayerConfig); + result->writableResult().setFlushConfig(configSnapshot.getMaintenanceConfigSP()->getFlushConfig()); return result; } @@ -454,6 +455,11 @@ StoreOnlyDocSubDB::reconfigure(const search::LogDocumentStore::Config & config) _rSummaryMgr->reconfigure(config); } +void +StoreOnlyDocSubDB::setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &) +{ +} + proton::IAttributeManager::SP StoreOnlyDocSubDB::getAttributeManager() const { diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h index f5038c252a7..d7b242f27ff 100644 --- a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h +++ b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.h @@ -215,6 +215,7 @@ public: IReprocessingTask::List applyConfig(const DocumentDBConfig &newConfigSnapshot, const DocumentDBConfig &oldConfigSnapshot, SerialNum serialNum, const ReconfigParams ¶ms, IDocumentDBReferenceResolver &resolver) override; + virtual void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &calc) override; ISearchHandler::SP getSearchView() const override { return _iSearchView.get(); } IFeedView::SP getFeedView() const override { return _iFeedView.get(); } diff --git a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp index 95f31f141d7..a271e34b6b7 100644 --- a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.cpp @@ -16,7 +16,8 @@ TransactionLogManagerBase::TransactionLogManagerBase( _tlc(tlsSpec), _tlcSession(), _domainName(domainName), - _replayMonitor(), + _replayLock(), + _replayCond(), _replayDone(false), _replayStarted(false), _replayStartTime(0) @@ -65,7 +66,7 @@ TransactionLogManagerBase::init() void TransactionLogManagerBase::internalStartReplay() { - vespalib::MonitorGuard guard(_replayMonitor); + std::lock_guard<std::mutex> guard(_replayLock); _replayStarted = true; _replayDone = false; FastOS_Time timer; @@ -76,23 +77,23 @@ TransactionLogManagerBase::internalStartReplay() void TransactionLogManagerBase::markReplayStarted() { - vespalib::MonitorGuard guard(_replayMonitor); + std::lock_guard<std::mutex> guard(_replayLock); _replayStarted = true; } void TransactionLogManagerBase::changeReplayDone() { - vespalib::MonitorGuard guard(_replayMonitor); + std::lock_guard<std::mutex> guard(_replayLock); _replayDone = true; - guard.broadcast(); + _replayCond.notify_all(); } void TransactionLogManagerBase::waitForReplayDone() const { - vespalib::MonitorGuard guard(_replayMonitor); + std::unique_lock<std::mutex> guard(_replayLock); while (_replayStarted && !_replayDone) { - guard.wait(); + _replayCond.wait(guard); } } @@ -115,12 +116,12 @@ TransLogClient::Visitor::UP TransactionLogManagerBase::createTlcVisitor( } bool TransactionLogManagerBase::getReplayDone() const { - vespalib::MonitorGuard guard(_replayMonitor); + std::lock_guard<std::mutex> guard(_replayLock); return _replayDone; } bool TransactionLogManagerBase::isDoingReplay() const { - vespalib::MonitorGuard guard(_replayMonitor); + std::lock_guard<std::mutex> guard(_replayLock); return _replayStarted && !_replayDone; } diff --git a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h index 1b109d8d9e1..9f4e63842cd 100644 --- a/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h +++ b/searchcore/src/vespa/searchcore/proton/server/transactionlogmanagerbase.h @@ -3,6 +3,8 @@ #pragma once #include <vespa/searchlib/transactionlog/translogclient.h> +#include <mutex> +#include <condition_variable> namespace proton { @@ -14,7 +16,8 @@ class TransactionLogManagerBase { search::transactionlog::TransLogClient _tlc; search::transactionlog::TransLogClient::Session::UP _tlcSession; vespalib::string _domainName; - vespalib::Monitor _replayMonitor; + mutable std::mutex _replayLock; + mutable std::condition_variable _replayCond; volatile bool _replayDone; bool _replayStarted; double _replayStartTime; diff --git a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp index 430f61eed1d..dd12a5cfd3d 100644 --- a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.cpp @@ -26,7 +26,7 @@ void VisibilityHandler::commit() if (_writeService.master().isCurrentThread()) { performCommit(true); } else { - LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); startCommit(guard, true); } } @@ -38,7 +38,7 @@ void VisibilityHandler::commitAndWait() if (_writeService.master().isCurrentThread()) { performCommit(false); } else { - LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); if (startCommit(guard, false)) { _writeService.master().sync(); } @@ -50,7 +50,7 @@ void VisibilityHandler::commitAndWait() _writeService.summary().sync(); } -bool VisibilityHandler::startCommit(const LockGuard &unused, bool force) +bool VisibilityHandler::startCommit(const std::lock_guard<std::mutex> &unused, bool force) { (void) unused; SerialNum current = _serial.getSerialNum(); diff --git a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h index 6de6b0721d6..6ba2b640fce 100644 --- a/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h +++ b/searchcore/src/vespa/searchcore/proton/server/visibilityhandler.h @@ -7,6 +7,7 @@ #include <vespa/searchcore/proton/server/igetserialnum.h> #include <vespa/searchcorespi/index/ithreadingservice.h> #include <vespa/vespalib/util/varholder.h> +#include <mutex> namespace proton { @@ -16,11 +17,9 @@ namespace proton { **/ class VisibilityHandler : public ICommitable { - typedef vespalib::LockGuard LockGuard; typedef fastos::TimeStamp TimeStamp; using IThreadingService = searchcorespi::index::IThreadingService; typedef vespalib::ThreadExecutor ThreadExecutor; - typedef vespalib::Lock Lock; typedef vespalib::VarHolder<IFeedView::SP> FeedViewHolder; public: typedef search::SerialNum SerialNum; @@ -32,14 +31,14 @@ public: void commit() override; virtual void commitAndWait() override; private: - bool startCommit(const LockGuard &unused, bool force); + bool startCommit(const std::lock_guard<std::mutex> &unused, bool force); void performCommit(bool force); const IGetSerialNum & _serial; IThreadingService & _writeService; const FeedViewHolder & _feedView; TimeStamp _visibilityDelay; SerialNum _lastCommitSerialNum; - Lock _lock; + std::mutex _lock; }; } diff --git a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp index cda1d0103c7..30e9382ae14 100644 --- a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp +++ b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.cpp @@ -51,7 +51,7 @@ SummaryEngine::close() { LOG(debug, "Closing summary engine"); { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _closed = true; } LOG(debug, "Handshaking with task manager"); @@ -61,21 +61,21 @@ SummaryEngine::close() ISearchHandler::SP SummaryEngine::putSearchHandler(const DocTypeName &docTypeName, const ISearchHandler::SP & searchHandler) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _handlers.putHandler(docTypeName, searchHandler); } ISearchHandler::SP SummaryEngine::getSearchHandler(const DocTypeName &docTypeName) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _handlers.getHandler(docTypeName); } ISearchHandler::SP SummaryEngine::removeSearchHandler(const DocTypeName &docTypeName) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _handlers.removeHandler(docTypeName); } @@ -107,7 +107,7 @@ SummaryEngine::getDocsums(DocsumRequest::UP req) } else { vespalib::Sequence<ISearchHandler*>::UP snapshot; { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); snapshot = _handlers.snapshot(); } if (snapshot->valid()) { diff --git a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h index d599f1521ec..2420a656909 100644 --- a/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h +++ b/searchcore/src/vespa/searchcore/proton/summaryengine/summaryengine.h @@ -6,6 +6,7 @@ #include <vespa/searchlib/engine/docsumapi.h> #include <vespa/vespalib/util/threadstackexecutor.h> #include <vespa/searchcore/proton/common/doctypename.h> +#include <mutex> namespace proton { @@ -16,7 +17,7 @@ private: using DocsumRequest = search::engine::DocsumRequest; using DocsumClient = search::engine::DocsumClient; - vespalib::Lock _lock; + std::mutex _lock; bool _closed; HandlerMap<ISearchHandler> _handlers; vespalib::ThreadStackExecutor _executor; diff --git a/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h b/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h index 6620795483d..66ae296566f 100644 --- a/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h +++ b/searchcore/src/vespa/searchcore/proton/test/dummy_document_sub_db.h @@ -55,6 +55,7 @@ struct DummyDocumentSubDb : public IDocumentSubDB { return IReprocessingTask::List(); } + void setBucketStateCalculator(const std::shared_ptr<IBucketStateCalculator> &) override { } ISearchHandler::SP getSearchView() const override { return ISearchHandler::SP(); } IFeedView::SP getFeedView() const override { return IFeedView::SP(); } void clearViews() override {} diff --git a/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h b/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h index 9e26b1250ff..9e105ef8a7f 100644 --- a/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h +++ b/searchcore/src/vespa/searchcore/proton/test/mock_index_manager.h @@ -28,6 +28,7 @@ struct MockIndexManager : public searchcorespi::IIndexManager } virtual void setSchema(const Schema &, SerialNum) override {} virtual void heartBeat(SerialNum) override {} + virtual void setMaxFlushed(uint32_t) override { } }; } // namespace test diff --git a/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h b/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h index a80eb7b670e..648ca0aa8ad 100644 --- a/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h +++ b/searchcore/src/vespa/searchcore/proton/test/simple_job_tracker.h @@ -2,7 +2,7 @@ #pragma once #include <vespa/searchcore/proton/metrics/i_job_tracker.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/count_down_latch.h> namespace proton { namespace test { diff --git a/searchcorespi/src/tests/plugin/plugin.cpp b/searchcorespi/src/tests/plugin/plugin.cpp index bea732f3db6..5614ea1da3a 100644 --- a/searchcorespi/src/tests/plugin/plugin.cpp +++ b/searchcorespi/src/tests/plugin/plugin.cpp @@ -35,6 +35,7 @@ public: return l; } virtual void setSchema(const Schema &, SerialNum) override { } + virtual void setMaxFlushed(uint32_t) override { } }; class IndexManagerFactory : public searchcorespi::IIndexManagerFactory diff --git a/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h b/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h index b6538bd1ade..c529fe01dee 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h +++ b/searchcorespi/src/vespa/searchcorespi/index/iindexmanager.h @@ -157,6 +157,13 @@ public: * @param schema The new schema to start using. **/ virtual void setSchema(const Schema &schema, SerialNum serialNum) = 0; + + /* + * Sets the max number of flushed indexes before fusion is urgent. + * + * @param maxFlushed The max number of flushed indexes before fusion is urgent. + */ + virtual void setMaxFlushed(uint32_t maxFlushed) = 0; }; } // namespace searchcorespi diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp index d18ff417074..a18d24931cb 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp +++ b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp @@ -1085,9 +1085,9 @@ IndexMaintainer::getFusionStats() const { LockGuard lock(_new_search_lock); source_list = _source_list; + stats.maxFlushed = _maxFlushed; } stats.diskUsage = source_list->getSearchableStats().sizeOnDisk(); - stats.maxFlushed = _maxFlushed; { LockGuard guard(_fusion_lock); stats.numUnfused = _fusion_spec.flush_ids.size() + ((_fusion_spec.last_fusion_id != 0) ? 1 : 0); @@ -1228,5 +1228,12 @@ IndexMaintainer::pruneRemovedFields(const Schema &schema, SerialNum serialNum) } } +void +IndexMaintainer::setMaxFlushed(uint32_t maxFlushed) +{ + LockGuard lock(_new_search_lock); + _maxFlushed = maxFlushed; +} + } // namespace index } // namespace searchcorespi diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h index 133784f3b57..fb0bc2e5e78 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h +++ b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.h @@ -383,6 +383,7 @@ public: IFlushTarget::List getFlushTargets() override; void setSchema(const Schema & schema, SerialNum serialNum) override ; + void setMaxFlushed(uint32_t maxFlushed) override; }; } // namespace index diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp b/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp index 3d3231a5e09..c8b2e81a9c0 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp +++ b/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp @@ -7,6 +7,7 @@ #include <vespa/fastlib/io/bufferedfile.h> #include <vespa/vespalib/util/exceptions.h> #include <sstream> +#include <unistd.h> #include <vespa/log/log.h> LOG_SETUP(".searchcorespi.index.indexwriteutilities"); diff --git a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp index cd0a4caf33b..75412dcf8e9 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp +++ b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.cpp @@ -117,7 +117,7 @@ WarmupIndexCollection::fireWarmup(Task::UP task) if (now < _warmupEndTime) { _executor.execute(std::move(task)); } else { - vespalib::LockGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); if (_warmupEndTime != 0) { _warmupEndTime = 0; guard.unlock(); @@ -133,7 +133,7 @@ WarmupIndexCollection::handledBefore(uint32_t fieldId, const Node &term) const StringBase * sb(dynamic_cast<const StringBase *>(&term)); if (sb != NULL) { const vespalib::string & s = sb->getTerm(); - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); TermMap::insert_result found = (*_handledTerms)[fieldId].insert(s); return ! found.second; } diff --git a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h index 96f46610a29..f6d6bc89fc4 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h +++ b/searchcorespi/src/vespa/searchcorespi/index/warmupindexcollection.h @@ -99,7 +99,7 @@ private: vespalib::ThreadExecutor & _executor; IWarmupDone & _warmupDone; fastos::TimeStamp _warmupEndTime; - vespalib::Lock _lock; + std::mutex _lock; std::unique_ptr<FieldTermMap> _handledTerms; }; diff --git a/searchlib/pom.xml b/searchlib/pom.xml index c669903c3da..5f6717d9516 100644 --- a/searchlib/pom.xml +++ b/searchlib/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>searchlib</artifactId> <packaging>container-plugin</packaging> diff --git a/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp b/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp index 8eef498d85c..5722b7c90ca 100644 --- a/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp +++ b/searchlib/src/tests/attribute/benchmark/attributebenchmark.cpp @@ -8,11 +8,12 @@ #include <vespa/searchlib/attribute/singlestringattribute.h> #include <vespa/searchlib/attribute/multistringattribute.h> #include <vespa/searchlib/attribute/attrvector.h> +#include <vespa/fastos/thread.h> #include <vespa/fastos/app.h> #include <iostream> #include <fstream> -#include "../attributesearcher.h" -#include "../attributeupdater.h" +#include "attributesearcher.h" +#include "attributeupdater.h" #include <sys/resource.h> #include <vespa/log/log.h> @@ -21,8 +22,6 @@ LOG_SETUP("attributebenchmark"); #include <vespa/searchlib/attribute/attributevector.hpp> -using vespalib::Monitor; -using vespalib::MonitorGuard; using std::shared_ptr; typedef std::vector<uint32_t> NumVector; @@ -268,11 +267,11 @@ AttributeBenchmark::benchmarkSearch(const AttributePtr & ptr, const std::vector< for (uint32_t i = 0; i < _config._numSearchers; ++i) { if (_config._rangeSearch) { RangeSpec spec(_config._rangeStart, _config._rangeEnd, _config._rangeDelta); - searchers.push_back(new AttributeRangeSearcher(i, ptr, spec, _config._numQueries)); + searchers.push_back(new AttributeRangeSearcher(ptr, spec, _config._numQueries)); } else if (_config._prefixSearch) { - searchers.push_back(new AttributePrefixSearcher(i, ptr, prefixStrings, _config._numQueries)); + searchers.push_back(new AttributePrefixSearcher(ptr, prefixStrings, _config._numQueries)); } else { - searchers.push_back(new AttributeFindSearcher<T>(i, ptr, values, _config._numQueries)); + searchers.push_back(new AttributeFindSearcher<T>(ptr, values, _config._numQueries)); } _threadPool->NewThread(searchers.back()); } diff --git a/searchlib/src/tests/attribute/attributesearcher.h b/searchlib/src/tests/attribute/benchmark/attributesearcher.h index bd86f291f7b..f8cd614c48c 100644 --- a/searchlib/src/tests/attribute/attributesearcher.h +++ b/searchlib/src/tests/attribute/benchmark/attributesearcher.h @@ -2,7 +2,7 @@ #pragma once -#include "runnable.h" +#include <vespa/searchlib/util/runnable.h> #include <vespa/searchlib/attribute/attribute.h> #include <vespa/searchlib/attribute/attributeguard.h> #include <vespa/searchlib/queryeval/hitcollector.h> @@ -67,8 +67,8 @@ protected: AttributeSearcherStatus _status; public: - AttributeSearcher(uint32_t id, const AttributePtr & attrPtr) : - Runnable(id), _attrPtr(attrPtr), _timer(), _status() + AttributeSearcher(const AttributePtr & attrPtr) : + Runnable(), _attrPtr(attrPtr), _timer(), _status() { _status._numClients = 1; } @@ -108,9 +108,9 @@ private: std::vector<char> _query; public: - AttributeFindSearcher(uint32_t id, const AttributePtr & attrPtr, const std::vector<T> & values, + AttributeFindSearcher(const AttributePtr & attrPtr, const std::vector<T> & values, uint32_t numQueries) : - AttributeSearcher(id, attrPtr), _values(values), _query() + AttributeSearcher(attrPtr), _values(values), _query() { _status._numQueries = numQueries; } @@ -186,9 +186,9 @@ private: std::vector<char> _query; public: - AttributeRangeSearcher(uint32_t id, const AttributePtr & attrPtr, const RangeSpec & spec, + AttributeRangeSearcher(const AttributePtr & attrPtr, const RangeSpec & spec, uint32_t numQueries) : - AttributeSearcher(id, attrPtr), _spec(spec), _query() + AttributeSearcher(attrPtr), _spec(spec), _query() { _status._numQueries = numQueries; } @@ -228,9 +228,9 @@ private: std::vector<char> _query; public: - AttributePrefixSearcher(uint32_t id, const AttributePtr & attrPtr, + AttributePrefixSearcher(const AttributePtr & attrPtr, const std::vector<vespalib::string> & values, uint32_t numQueries) : - AttributeSearcher(id, attrPtr), _values(values), _query() + AttributeSearcher(attrPtr), _values(values), _query() { _status._numQueries = numQueries; } diff --git a/searchlib/src/tests/attribute/attributeupdater.h b/searchlib/src/tests/attribute/benchmark/attributeupdater.h index 3f41b79f331..13360e58b2d 100644 --- a/searchlib/src/tests/attribute/attributeupdater.h +++ b/searchlib/src/tests/attribute/benchmark/attributeupdater.h @@ -3,7 +3,7 @@ #pragma once #include <vespa/searchlib/util/randomgenerator.h> -#include "runnable.h" +#include <vespa/searchlib/util/runnable.h> #include <vespa/searchlib/attribute/attribute.h> #define VALIDATOR_STR(str) #str @@ -169,7 +169,7 @@ AttributeUpdaterThread<Vector, T, BT>::AttributeUpdaterThread(const AttributePtr RandomGenerator & rndGen, bool validate, uint32_t commitFreq, uint32_t minValueCount, uint32_t maxValueCount) : AttributeUpdater<Vector, T, BT>(attrPtr, values, rndGen, validate, commitFreq, minValueCount, maxValueCount), - Runnable(0) + Runnable() {} template <typename Vector, typename T, typename BT> AttributeUpdaterThread<Vector, T, BT>::~AttributeUpdaterThread() { } diff --git a/searchlib/src/tests/attribute/runnable.h b/searchlib/src/tests/attribute/runnable.h deleted file mode 100644 index b95d7d3843f..00000000000 --- a/searchlib/src/tests/attribute/runnable.h +++ /dev/null @@ -1,43 +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 <vespa/vespalib/util/sync.h> -#include <vespa/fastos/thread.h> - -namespace search { - -class Runnable : public FastOS_Runnable -{ -protected: - uint32_t _id; - vespalib::Monitor _cond; - bool _done; - bool _stopped; - -public: - Runnable(uint32_t id) : - _id(id), _cond(), _done(false), _stopped(false) - { } - void Run(FastOS_ThreadInterface *, void *) override { - doRun(); - - vespalib::MonitorGuard guard(_cond); - _stopped = true; - guard.broadcast(); - } - virtual void doRun() = 0; - void stop() { - vespalib::MonitorGuard guard(_cond); - _done = true; - } - void join() { - vespalib::MonitorGuard guard(_cond); - while (!_stopped) { - guard.wait(); - } - } -}; - -} // search - diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp index 8fc47725a1d..d041dde52a5 100644 --- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp +++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp @@ -363,10 +363,10 @@ Fixture::testEmptyTensor() Tensor::UP emptyTensor = tensorAttr.getEmptyTensor(); if (_denseTensors) { vespalib::string expSpec = expEmptyDenseTensorSpec(); - EXPECT_EQUAL(emptyTensor->getType(), ValueType::from_spec(expSpec)); + EXPECT_EQUAL(emptyTensor->type(), ValueType::from_spec(expSpec)); } else { - EXPECT_EQUAL(emptyTensor->getType(), tensorAttr.getConfig().tensorType()); - EXPECT_EQUAL(emptyTensor->getType(), ValueType::from_spec(_typeSpec)); + EXPECT_EQUAL(emptyTensor->type(), tensorAttr.getConfig().tensorType()); + EXPECT_EQUAL(emptyTensor->type(), ValueType::from_spec(_typeSpec)); } } diff --git a/searchlib/src/tests/features/constant/constant_test.cpp b/searchlib/src/tests/features/constant/constant_test.cpp index 4a88fde58ce..f9d1736ba9e 100644 --- a/searchlib/src/tests/features/constant/constant_test.cpp +++ b/searchlib/src/tests/features/constant/constant_test.cpp @@ -76,7 +76,7 @@ struct ExecFixture const TensorDimensions &dimensions) { Tensor::UP tensor = createTensor(cells, dimensions); - ValueType type(tensor->getType()); + ValueType type(tensor->type()); test.getIndexEnv().addConstantValue(name, std::move(type), std::move(tensor)); diff --git a/searchlib/src/tests/transactionlog/translogclient_test.cpp b/searchlib/src/tests/transactionlog/translogclient_test.cpp index 9f83db9b23a..861023b79b7 100644 --- a/searchlib/src/tests/transactionlog/translogclient_test.cpp +++ b/searchlib/src/tests/transactionlog/translogclient_test.cpp @@ -43,12 +43,12 @@ private: void checkFilledDomainTest(const TransLogClient::Session::UP &s1, size_t numEntries); bool visitDomainTest(TransLogClient & tls, TransLogClient::Session * s1, const vespalib::string & name); bool partialUpdateTest(); - bool test1(); + bool testVisitOverGeneratedDomain(); bool testRemove(); void createAndFillDomain(const vespalib::string & name, DomainPart::Crc crcMethod, size_t preExistingDomains); void verifyDomain(const vespalib::string & name); void testCrcVersions(); - bool test2(); + bool testVisitOverPreExistingDomain(); void testMany(); void testErase(); void testSync(); @@ -480,7 +480,13 @@ bool Test::visitDomainTest(TransLogClient & tls, TransLogClient::Session * s1, c return retval; } -bool Test::test1() +double +getMaxSessionRunTime(TransLogServer &tls, const vespalib::string &domain) +{ + return tls.getDomainStats()[domain].maxSessionRunTime.count(); +} + +bool Test::testVisitOverGeneratedDomain() { DummyFileHeaderContext fileHeaderContext; TransLogServer tlss("test7", 18377, ".", fileHeaderContext, 0x10000); @@ -490,7 +496,11 @@ bool Test::test1() createDomainTest(tls, name); TransLogClient::Session::UP s1 = openDomainTest(tls, name); fillDomainTest(s1.get(), name); + EXPECT_EQUAL(0, getMaxSessionRunTime(tlss, "test1")); visitDomainTest(tls, s1.get(), name); + double maxSessionRunTime = getMaxSessionRunTime(tlss, "test1"); + LOG(info, "testVisitOverGeneratedDomain(): maxSessionRunTime=%f", maxSessionRunTime); + EXPECT_GREATER(maxSessionRunTime, 0); return true; } @@ -539,8 +549,9 @@ bool Test::testRemove() return true; } -bool Test::test2() +bool Test::testVisitOverPreExistingDomain() { + // Depends on Test::testVisitOverGeneratedDomain() DummyFileHeaderContext fileHeaderContext; TransLogServer tlss("test7", 18377, ".", fileHeaderContext, 0x10000); TransLogClient tls("tcp/localhost:18377"); @@ -868,8 +879,8 @@ int Test::Main() if (_argc > 0) { DummyFileHeaderContext::setCreator(_argv[0]); } - test1(); - test2(); + testVisitOverGeneratedDomain(); + testVisitOverPreExistingDomain(); testMany(); testErase(); partialUpdateTest(); diff --git a/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp b/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp index 770d5c653a3..0a3fe963aae 100644 --- a/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp +++ b/searchlib/src/vespa/searchlib/attribute/attributecontext.cpp @@ -43,19 +43,19 @@ AttributeContext::~AttributeContext() { } const IAttributeVector * AttributeContext::getAttribute(const string & name) const { - vespalib::LockGuard guard(_cacheLock); + std::lock_guard<std::mutex> guard(_cacheLock); return getAttribute(_attributes, name, false); } const IAttributeVector * AttributeContext::getAttributeStableEnum(const string & name) const { - vespalib::LockGuard guard(_cacheLock); + std::lock_guard<std::mutex> guard(_cacheLock); return getAttribute(_enumAttributes, name, true); } void AttributeContext::releaseEnumGuards() { - vespalib::LockGuard guard(_cacheLock); + std::lock_guard<std::mutex> guard(_cacheLock); _enumAttributes.clear(); } diff --git a/searchlib/src/vespa/searchlib/attribute/attributecontext.h b/searchlib/src/vespa/searchlib/attribute/attributecontext.h index b7373f343ec..80abe84f8ef 100644 --- a/searchlib/src/vespa/searchlib/attribute/attributecontext.h +++ b/searchlib/src/vespa/searchlib/attribute/attributecontext.h @@ -3,9 +3,9 @@ #pragma once #include <vespa/searchcommon/attribute/iattributecontext.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/stllike/hash_map.h> #include "iattributemanager.h" +#include <mutex> namespace search { @@ -21,7 +21,7 @@ private: const search::IAttributeManager & _manager; mutable AttributeMap _attributes; mutable AttributeMap _enumAttributes; - mutable vespalib::Lock _cacheLock; + mutable std::mutex _cacheLock; const attribute::IAttributeVector * getAttribute(AttributeMap & map, const string & name, bool stableEnum) const; diff --git a/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp b/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp index 0122516b767..f10f3491ac0 100644 --- a/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp +++ b/searchlib/src/vespa/searchlib/attribute/attributemanager.cpp @@ -12,14 +12,14 @@ #include <vespa/log/log.h> LOG_SETUP(".searchlib.attributemanager"); -using vespalib::LockGuard; using vespalib::string; using vespalib::IllegalStateException; using search::attribute::IAttributeContext; namespace { -vespalib::Monitor baseDirMonitor; +std::mutex baseDirLock; +std::condition_variable baseDirCond; typedef std::set<string> BaseDirSet; BaseDirSet baseDirSet; @@ -27,7 +27,7 @@ static void waitBaseDir(const string &baseDir) { if (baseDir.empty()) { return; } - vespalib::MonitorGuard guard(baseDirMonitor); + std::unique_lock<std::mutex> guard(baseDirLock); bool waited = false; BaseDirSet::iterator it = baseDirSet.find(baseDir); @@ -36,7 +36,7 @@ waitBaseDir(const string &baseDir) waited = true; LOG(debug, "AttributeManager: Waiting for basedir %s to be available", baseDir.c_str()); } - guard.wait(); + baseDirCond.wait(guard); it = baseDirSet.find(baseDir); } @@ -52,7 +52,7 @@ dropBaseDir(const string &baseDir) { if (baseDir.empty()) return; - vespalib::MonitorGuard guard(baseDirMonitor); + std::lock_guard<std::mutex> guard(baseDirLock); BaseDirSet::iterator it = baseDirSet.find(baseDir); if (it == baseDirSet.end()) { @@ -60,7 +60,7 @@ dropBaseDir(const string &baseDir) } else { baseDirSet.erase(it); } - guard.broadcast(); + baseDirCond.notify_all(); } } @@ -147,7 +147,7 @@ AttributeManager::findAndLoadAttribute(const string & name) const if (found != _attributes.end()) { AttributeVector & vec = *found->second; if ( ! vec.isLoaded() ) { - vespalib::LockGuard loadGuard(_loadLock); + std::lock_guard<std::mutex> loadGuard(_loadLock); if ( ! vec.isLoaded() ) { vec.load(); } else { diff --git a/searchlib/src/vespa/searchlib/attribute/attributemanager.h b/searchlib/src/vespa/searchlib/attribute/attributemanager.h index f69d4cb9295..3b23881e3bc 100644 --- a/searchlib/src/vespa/searchlib/attribute/attributemanager.h +++ b/searchlib/src/vespa/searchlib/attribute/attributemanager.h @@ -7,7 +7,7 @@ #include <vespa/searchlib/common/indexmetainfo.h> #include <vespa/searchcommon/attribute/config.h> #include <vespa/vespalib/stllike/hash_map.h> -#include <vespa/vespalib/util/sync.h> +#include <mutex> namespace search { @@ -56,7 +56,7 @@ public: protected: typedef vespalib::hash_map<string, VectorHolder> AttributeMap; AttributeMap _attributes; - vespalib::Lock _loadLock; + mutable std::mutex _loadLock; private: const VectorHolder * findAndLoadAttribute(const string & name) const; string createBaseFileName(const string & name, bool useSnapshot) const; diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.h b/searchlib/src/vespa/searchlib/attribute/attributevector.h index 13ba3f801b7..b514a5830ba 100644 --- a/searchlib/src/vespa/searchlib/attribute/attributevector.h +++ b/searchlib/src/vespa/searchlib/attribute/attributevector.h @@ -19,8 +19,7 @@ #include <vespa/searchlib/queryeval/searchiterator.h> #include <vespa/vespalib/objects/identifiable.h> #include <vespa/vespalib/stllike/asciistream.h> -#include <vespa/vespalib/util/rwlock.h> -#include <vespa/vespalib/util/sync.h> +#include <vespa/fastos/time.h> #include <cmath> #include <mutex> #include <shared_mutex> diff --git a/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp b/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp index 04909da8ea7..a063ed347a4 100644 --- a/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp +++ b/searchlib/src/vespa/searchlib/common/bitvectorcache.cpp @@ -29,7 +29,7 @@ BitVectorCache::computeCountVector(KeySet & keys, CountVector & v) const std::vector<CondensedBitVector::KeySet> keySets; ChunkV chunks; { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); keySets.resize(_chunks.size()); Key2Index::const_iterator end(_keys.end()); for (Key k : keys) { @@ -61,7 +61,7 @@ BitVectorCache::KeySet BitVectorCache::lookupCachedSet(const KeyAndCountSet & keys) { KeySet cached(keys.size()*3); - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _lookupCount++; if (_lookupCount == 2000) { _needPopulation = true; @@ -101,7 +101,7 @@ BitVectorCache::getSorted(Key2Index & keys) } bool -BitVectorCache::hasCostChanged(const vespalib::LockGuard & guard) +BitVectorCache::hasCostChanged(const std::lock_guard<std::mutex> & guard) { (void) guard; if ( ! _chunks.empty()) { @@ -163,17 +163,17 @@ BitVectorCache::populate(Key2Index & newKeys, CondensedBitVector & chunk, const void BitVectorCache::populate(uint32_t sz, const PopulateInterface & lookup) { - vespalib::LockGuard guard1(_lock); + std::unique_lock<std::mutex> guard(_lock); if (! _needPopulation) { return; } Key2Index newKeys(_keys); - guard1.unlock(); + guard.unlock(); CondensedBitVector::UP chunk(CondensedBitVector::create(sz, _genHolder)); populate(newKeys, *chunk, lookup); - vespalib::LockGuard guard2(_lock); + guard.lock(); _chunks.push_back(std::move(chunk)); _keys.swap(newKeys); _needPopulation = false; @@ -182,7 +182,7 @@ BitVectorCache::populate(uint32_t sz, const PopulateInterface & lookup) void BitVectorCache::set(Key key, uint32_t index, bool v) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); auto found = _keys.find(key); if (found != _keys.end()) { const KeyMeta & m(found->second); @@ -202,7 +202,7 @@ BitVectorCache::get(Key key, uint32_t index) const void BitVectorCache::removeIndex(uint32_t index) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); for (auto & chunk : _chunks) { chunk->clearIndex(index); } diff --git a/searchlib/src/vespa/searchlib/common/bitvectorcache.h b/searchlib/src/vespa/searchlib/common/bitvectorcache.h index 0d27701e1e1..c1415d9130f 100644 --- a/searchlib/src/vespa/searchlib/common/bitvectorcache.h +++ b/searchlib/src/vespa/searchlib/common/bitvectorcache.h @@ -2,9 +2,9 @@ #pragma once #include "condensedbitvectors.h" -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/stllike/hash_set.h> #include <vespa/fastos/dynamiclibrary.h> +#include <mutex> namespace search { @@ -74,11 +74,11 @@ private: VESPA_DLL_LOCAL static SortedKeyMeta getSorted(Key2Index & keys); VESPA_DLL_LOCAL static void populate(Key2Index & newKeys, CondensedBitVector & chunk, const PopulateInterface & lookup); - VESPA_DLL_LOCAL bool hasCostChanged(const vespalib::LockGuard &); + VESPA_DLL_LOCAL bool hasCostChanged(const std::lock_guard<std::mutex> &); uint64_t _lookupCount; bool _needPopulation; - vespalib::Lock _lock; + mutable std::mutex _lock; Key2Index _keys; ChunkV _chunks; GenerationHolder &_genHolder; diff --git a/searchlib/src/vespa/searchlib/common/gatecallback.cpp b/searchlib/src/vespa/searchlib/common/gatecallback.cpp index a853909be71..29346d7ad9c 100644 --- a/searchlib/src/vespa/searchlib/common/gatecallback.cpp +++ b/searchlib/src/vespa/searchlib/common/gatecallback.cpp @@ -1,7 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "gatecallback.h" -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> namespace search { diff --git a/searchlib/src/vespa/searchlib/common/sortspec.cpp b/searchlib/src/vespa/searchlib/common/sortspec.cpp index f43596f9c7f..694443b00ba 100644 --- a/searchlib/src/vespa/searchlib/common/sortspec.cpp +++ b/searchlib/src/vespa/searchlib/common/sortspec.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 "sortspec.h" #include <vespa/vespalib/util/stringfmt.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/fastlib/text/normwordfolder.h> #include <vespa/vespalib/text/utf8.h> diff --git a/searchlib/src/vespa/searchlib/features/queryfeature.cpp b/searchlib/src/vespa/searchlib/features/queryfeature.cpp index 271861d472c..3e52546e841 100644 --- a/searchlib/src/vespa/searchlib/features/queryfeature.cpp +++ b/searchlib/src/vespa/searchlib/features/queryfeature.cpp @@ -122,7 +122,7 @@ createTensorExecutor(const search::fef::IQueryEnvironment &env, const vespalib::string &value = prop.get(); vespalib::nbostream stream(value.data(), value.size()); vespalib::tensor::Tensor::UP tensor = vespalib::tensor::TypedBinaryFormat::deserialize(stream); - if (tensor->getType() != valueType) { + if (tensor->type() != valueType) { vespalib::tensor::TensorMapper mapper(valueType); vespalib::tensor::Tensor::UP mappedTensor = mapper.map(*tensor); tensor = std::move(mappedTensor); diff --git a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp index 74ca3e6527d..03785258f28 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp +++ b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp @@ -36,6 +36,7 @@ Domain::Domain(const string &domainName, const string & baseDir, Executor & comm _lock(), _sessionLock(), _sessions(), + _maxSessionRunTime(), _baseDir(baseDir), _fileHeaderContext(fileHeaderContext), _markedDeleted(false) @@ -105,7 +106,7 @@ DomainInfo Domain::getDomainInfo() const { LockGuard guard(_lock); - DomainInfo info(SerialNumRange(begin(guard), end(guard)), size(guard), byteSize(guard)); + DomainInfo info(SerialNumRange(begin(guard), end(guard)), size(guard), byteSize(guard), _maxSessionRunTime); for (const auto &entry: _parts) { const DomainPart &part = *entry.second; info.parts.emplace_back(PartInfo(part.range(), part.size(), part.byteSize(), part.fileName())); @@ -332,6 +333,7 @@ int Domain::startSession(int sessionId) LockGuard guard(_sessionLock); SessionList::iterator found = _sessions.find(sessionId); if (found != _sessions.end()) { + found->second->setStartTime(std::chrono::steady_clock::now()); if ( execute(Session::createTask(found->second)).get() == nullptr ) { retval = 0; } else { @@ -345,10 +347,12 @@ int Domain::closeSession(int sessionId) { _commitExecutor.sync(); int retval(-1); + DurationSeconds sessionRunTime(0); { LockGuard guard(_sessionLock); SessionList::iterator found = _sessions.find(sessionId); if (found != _sessions.end()) { + sessionRunTime = (std::chrono::steady_clock::now() - found->second->getStartTime()); retval = 1; _sessionExecutor.sync(); } @@ -364,6 +368,12 @@ int Domain::closeSession(int sessionId) retval = 0; } } + { + LockGuard guard(_lock); + if (sessionRunTime > _maxSessionRunTime) { + _maxSessionRunTime = sessionRunTime; + } + } return retval; } diff --git a/searchlib/src/vespa/searchlib/transactionlog/domain.h b/searchlib/src/vespa/searchlib/transactionlog/domain.h index ab7ded91e5b..c1ff9157a6f 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/domain.h +++ b/searchlib/src/vespa/searchlib/transactionlog/domain.h @@ -4,6 +4,7 @@ #include "domainpart.h" #include "session.h" #include <vespa/vespalib/util/threadexecutor.h> +#include <chrono> namespace search::transactionlog { @@ -20,14 +21,16 @@ struct PartInfo { }; struct DomainInfo { + using DurationSeconds = std::chrono::duration<double>; SerialNumRange range; size_t numEntries; size_t byteSize; + DurationSeconds maxSessionRunTime; std::vector<PartInfo> parts; - DomainInfo(SerialNumRange range_in, size_t numEntries_in, size_t byteSize_in) - : range(range_in), numEntries(numEntries_in), byteSize(byteSize_in), parts() {} + DomainInfo(SerialNumRange range_in, size_t numEntries_in, size_t byteSize_in, DurationSeconds maxSessionRunTime_in) + : range(range_in), numEntries(numEntries_in), byteSize(byteSize_in), maxSessionRunTime(maxSessionRunTime_in), parts() {} DomainInfo() - : range(), numEntries(0), byteSize(0), parts() {} + : range(), numEntries(0), byteSize(0), maxSessionRunTime(), parts() {} }; typedef std::map<vespalib::string, DomainInfo> DomainStats; @@ -74,6 +77,7 @@ public: return _sessionExecutor.execute(std::move(task)); } uint64_t size() const; + private: SerialNum begin(const vespalib::LockGuard & guard) const; SerialNum end(const vespalib::LockGuard & guard) const; @@ -89,6 +93,7 @@ private: using SessionList = std::map<int, Session::SP>; using DomainPartList = std::map<int64_t, DomainPart::SP>; + using DurationSeconds = std::chrono::duration<double>; DomainPart::Crc _defaultCrcType; Executor & _commitExecutor; @@ -102,6 +107,7 @@ private: vespalib::Lock _lock; vespalib::Lock _sessionLock; SessionList _sessions; + DurationSeconds _maxSessionRunTime; vespalib::string _baseDir; const common::FileHeaderContext &_fileHeaderContext; bool _markedDeleted; diff --git a/searchlib/src/vespa/searchlib/transactionlog/session.cpp b/searchlib/src/vespa/searchlib/transactionlog/session.cpp index 3ea656be9a2..6f0ab998e26 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/session.cpp +++ b/searchlib/src/vespa/searchlib/transactionlog/session.cpp @@ -197,7 +197,9 @@ Session::Session(int sId, const SerialNumRange & r, const Domain::SP & d, _inSync(false), _ok(true), _finished(false), - _packetQ() + _packetQ(), + _startTime(), + _lock() { _connection->AddRef(); } diff --git a/searchlib/src/vespa/searchlib/transactionlog/session.h b/searchlib/src/vespa/searchlib/transactionlog/session.h index ac6f496e151..c42d6839dfa 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/session.h +++ b/searchlib/src/vespa/searchlib/transactionlog/session.h @@ -5,6 +5,7 @@ #include <vespa/vespalib/util/executor.h> #include <vespa/vespalib/util/sync.h> #include <vespa/fnet/frt/invoker.h> +#include <chrono> #include <deque> class FastOS_FileInterface; @@ -18,7 +19,8 @@ using DomainSP = std::shared_ptr<Domain>; class Session : public FRT_IRequestWait { private: - typedef vespalib::Executor::Task Task; + using Task = vespalib::Executor::Task; + using time_point = std::chrono::time_point<std::chrono::steady_clock>; public: typedef std::shared_ptr<Session> SP; @@ -33,6 +35,8 @@ public: bool finished() const; static void enQ(const SP & session, SerialNum serial, const Packet & packet); static Task::UP createTask(const Session::SP & session); + void setStartTime(time_point startTime) { _startTime = startTime; } + time_point getStartTime() const { return _startTime; } private: struct QPacket { QPacket() : _serial(0), _packet() {} @@ -79,6 +83,7 @@ private: bool _ok; bool _finished; std::deque<QPacket> _packetQ; + time_point _startTime; vespalib::Lock _lock; }; diff --git a/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp b/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp index db16d165cec..47d66a94d9e 100644 --- a/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp +++ b/searchlib/src/vespa/searchlib/uca/ucaconverter.cpp @@ -2,8 +2,8 @@ #include "ucaconverter.h" #include <vespa/vespalib/util/stringfmt.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/text/utf8.h> +#include <mutex> #include <vespa/log/log.h> LOG_SETUP(".search.common.sortspec"); @@ -14,7 +14,7 @@ using vespalib::ConstBufferRef; using vespalib::make_string; namespace { - vespalib::Lock _GlobalDirtyICUThreadSafeLock; +std::mutex _GlobalDirtyICUThreadSafeLock; } BlobConverter::UP @@ -30,7 +30,7 @@ UcaConverter::UcaConverter(vespalib::stringref locale, vespalib::stringref stren UErrorCode status = U_ZERO_ERROR; Collator *coll(NULL); { - vespalib::LockGuard guard(_GlobalDirtyICUThreadSafeLock); + std::lock_guard<std::mutex> guard(_GlobalDirtyICUThreadSafeLock); coll = Collator::createInstance(icu::Locale(locale.c_str()), status); } if(U_SUCCESS(status)) { diff --git a/searchlib/src/vespa/searchlib/util/runnable.h b/searchlib/src/vespa/searchlib/util/runnable.h index a8dc8e56856..a95d4d68368 100644 --- a/searchlib/src/vespa/searchlib/util/runnable.h +++ b/searchlib/src/vespa/searchlib/util/runnable.h @@ -2,37 +2,39 @@ #pragma once -#include <vespa/vespalib/util/sync.h> +#include <mutex> +#include <condition_variable> namespace search { class Runnable : public FastOS_Runnable { protected: - vespalib::Monitor _cond; - bool _done; - bool _stopped; + std::mutex _lock; + std::condition_variable _cond; + bool _done; + bool _stopped; public: Runnable() : - _cond(), _done(false), _stopped(false) + _lock(), _cond(), _done(false), _stopped(false) { } - void Run(FastOS_ThreadInterface *, void *) override{ + void Run(FastOS_ThreadInterface *, void *) override { doRun(); - vespalib::MonitorGuard guard(_cond); + std::lock_guard<std::mutex> guard(_lock); _stopped = true; - guard.broadcast(); + _cond.notify_all(); } virtual void doRun() = 0; void stop() { - vespalib::MonitorGuard guard(_cond); + std::lock_guard<std::mutex> guard(_lock); _done = true; } void join() { - vespalib::MonitorGuard guard(_cond); + std::unique_lock<std::mutex> guard(_lock); while (!_stopped) { - guard.wait(); + _cond.wait(guard); } } }; diff --git a/searchsummary/pom.xml b/searchsummary/pom.xml index 97a29655ad1..417e25ff944 100644 --- a/searchsummary/pom.xml +++ b/searchsummary/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>searchsummary</artifactId> <version>6-SNAPSHOT</version> diff --git a/service-monitor/pom.xml b/service-monitor/pom.xml index c63bdf35574..70f9d4aa655 100644 --- a/service-monitor/pom.xml +++ b/service-monitor/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>service-monitor</artifactId> <packaging>container-plugin</packaging> @@ -66,6 +67,7 @@ <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <scope>provided</scope> + <classifier>no_aop</classifier> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> diff --git a/serviceview/pom.xml b/serviceview/pom.xml index c3620ecfc9d..4357cc697b8 100644 --- a/serviceview/pom.xml +++ b/serviceview/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>serviceview</artifactId> <packaging>container-plugin</packaging> diff --git a/simplemetrics/pom.xml b/simplemetrics/pom.xml index d027fda82ce..02758b182e3 100644 --- a/simplemetrics/pom.xml +++ b/simplemetrics/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>simplemetrics</artifactId> <packaging>container-plugin</packaging> diff --git a/socket_test/pom.xml b/socket_test/pom.xml index 60d8b3713e9..0c6d5cd77f4 100644 --- a/socket_test/pom.xml +++ b/socket_test/pom.xml @@ -12,6 +12,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <packaging>jar</packaging> <build> diff --git a/staging_vespalib/CMakeLists.txt b/staging_vespalib/CMakeLists.txt index d59529df828..095caf17a56 100644 --- a/staging_vespalib/CMakeLists.txt +++ b/staging_vespalib/CMakeLists.txt @@ -21,6 +21,7 @@ vespa_define_module( src/tests/json src/tests/librarypool src/tests/memorydatastore + src/tests/metrics src/tests/objectdump src/tests/objects src/tests/objectselection @@ -38,6 +39,7 @@ vespa_define_module( src/vespa/vespalib src/vespa/vespalib/data src/vespa/vespalib/encoding + src/vespa/vespalib/metrics src/vespa/vespalib/net src/vespa/vespalib/objects src/vespa/vespalib/stllike diff --git a/staging_vespalib/src/testlist.txt b/staging_vespalib/src/testlist.txt deleted file mode 100644 index f29e51218ec..00000000000 --- a/staging_vespalib/src/testlist.txt +++ /dev/null @@ -1,27 +0,0 @@ -tests/array -tests/benchmark -tests/bits -tests/clock -tests/crc -tests/databuffer -tests/directio -tests/dotproduct -tests/encoding/base64 -tests/fileheader -tests/floatingpointtype -tests/growablebytebuffer -tests/json -tests/librarypool -tests/memorydatastore -tests/objectdump -tests/objects -tests/objectselection -tests/programoptions -tests/polymorphicarray -tests/rusage -tests/shutdownguard -tests/state_server -tests/stllike -tests/timer -tests/util/process_memory_stats -tests/xmlserializable diff --git a/staging_vespalib/src/tests/metrics/CMakeLists.txt b/staging_vespalib/src/tests/metrics/CMakeLists.txt new file mode 100644 index 00000000000..a441631fd15 --- /dev/null +++ b/staging_vespalib/src/tests/metrics/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(staging_vespalib_metrics_test_app TEST + SOURCES + simple_metrics_test.cpp + mock_tick.cpp + DEPENDS + staging_vespalib +) +vespa_add_test(NAME staging_vespalib_metrics_test_app COMMAND staging_vespalib_metrics_test_app) + +vespa_add_executable(staging_vespalib_stablestore_test_app TEST + SOURCES + stable_store_test.cpp + DEPENDS + staging_vespalib +) +vespa_add_test(NAME staging_vespalib_stablestore_test_app COMMAND staging_vespalib_stablestore_test_app) diff --git a/staging_vespalib/src/tests/metrics/mock_tick.cpp b/staging_vespalib/src/tests/metrics/mock_tick.cpp new file mode 100644 index 00000000000..68e8d7a7daa --- /dev/null +++ b/staging_vespalib/src/tests/metrics/mock_tick.cpp @@ -0,0 +1,6 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "mock_tick.h" + +namespace vespalib::metrics { + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/tests/metrics/mock_tick.h b/staging_vespalib/src/tests/metrics/mock_tick.h new file mode 100644 index 00000000000..3f244ea6c9f --- /dev/null +++ b/staging_vespalib/src/tests/metrics/mock_tick.h @@ -0,0 +1,91 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <atomic> +#include <condition_variable> +#include <mutex> +#include <vespa/vespalib/metrics/clock.h> +#include <vespa/vespalib/testkit/test_kit.h> + +namespace vespalib::metrics { + +// used to test clients of the Tick interface +// values shared between threads are bounded queues with max size 1 +class MockTick : public Tick { +private: + using Guard = std::unique_lock<std::mutex>; + struct Value { + TimeStamp value{0.0}; + bool valid{false}; + }; + + TimeStamp _first_value; + std::mutex _lock; + std::condition_variable _cond; + bool _alive; + Value _prev; + Value _next; + + void push(Value &dst, TimeStamp value) { + Guard guard(_lock); + while (_alive && dst.valid) { + _cond.wait(guard); + } + dst.value = value; + dst.valid = true; + _cond.notify_one(); + } + + TimeStamp pop(Value &src) { + Guard guard(_lock); + while (_alive && !src.valid) { + _cond.wait(guard); + } + src.valid = false; + _cond.notify_one(); + return src.value; + } + + TimeStamp peek(const Value &src) { + Guard guard(_lock); + while (_alive && !src.valid) { + _cond.wait(guard); + } + return src.value; + } + +public: + MockTick(TimeStamp first_value) + : _first_value(first_value), _lock(), _cond(), _alive(true), _prev(), _next() {} + TimeStamp first() override { return _first_value; } + TimeStamp next(TimeStamp prev) override { + push(_prev, prev); + return pop(_next); + } + TimeStamp give(TimeStamp next_value) { + TimeStamp prev_value = pop(_prev); + push(_next, next_value); + EXPECT_EQUAL(peek(_prev).count(), next_value.count()); + return prev_value; + } + bool alive() const override { return _alive; } + void kill() override { + Guard guard(_lock); + _alive = false; + _cond.notify_all(); + } +}; + +// share the MockTick between the tested and the tester. +class TickProxy : public Tick { +private: + std::shared_ptr<Tick> _tick; +public: + TickProxy(std::shared_ptr<Tick> tick) : _tick(std::move(tick)) {} + TimeStamp first() override { return _tick->first(); } + TimeStamp next(TimeStamp prev) override { return _tick->next(prev); } + bool alive() const override { return _tick->alive(); } + void kill() override { _tick->kill(); } +}; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/tests/metrics/simple_metrics_test.cpp b/staging_vespalib/src/tests/metrics/simple_metrics_test.cpp new file mode 100644 index 00000000000..da2502f7ed7 --- /dev/null +++ b/staging_vespalib/src/tests/metrics/simple_metrics_test.cpp @@ -0,0 +1,215 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/data/slime/slime.h> +#include <vespa/vespalib/data/slime/json_format.h> +#include <vespa/vespalib/metrics/simple_metrics.h> +#include <vespa/vespalib/metrics/simple_metrics_manager.h> +#include <vespa/vespalib/metrics/stable_store.h> +#include <vespa/vespalib/metrics/json_formatter.h> +#include "mock_tick.h" +#include <stdio.h> +#include <unistd.h> + +using namespace vespalib; +using namespace vespalib::metrics; + +TEST("require that simple metrics gauge merge works") +{ + MetricIdentifier id(MetricName(42), Point(17)); + Gauge::Measurement a1(id, 0.0); + Gauge::Measurement b1(id, 7.0); + Gauge::Measurement b2(id, 9.0); + Gauge::Measurement b3(id, 8.0); + Gauge::Measurement c1(id, 10.0); + Gauge::Measurement c2(id, 1.0); + + GaugeAggregator a(a1), b(b1), c(c1); + b.merge(b2); + b.merge(b3); + c.merge(c2); + + EXPECT_EQUAL(a.observedCount, 1u); + EXPECT_EQUAL(a.sumValue, 0.0); + EXPECT_EQUAL(a.minValue, 0.0); + EXPECT_EQUAL(a.maxValue, 0.0); + EXPECT_EQUAL(a.lastValue, 0.0); + + EXPECT_EQUAL(b.observedCount, 3u); + EXPECT_EQUAL(b.sumValue, 24.0); + EXPECT_EQUAL(b.minValue, 7.0); + EXPECT_EQUAL(b.maxValue, 9.0); + EXPECT_EQUAL(b.lastValue, 8.0); + + EXPECT_EQUAL(c.observedCount, 2u); + EXPECT_EQUAL(c.sumValue, 11.0); + EXPECT_EQUAL(c.minValue, 1.0); + EXPECT_EQUAL(c.maxValue, 10.0); + EXPECT_EQUAL(c.lastValue, 1.0); + + a.minValue = 8; + + a.merge(b); + EXPECT_EQUAL(a.observedCount, 4u); + EXPECT_EQUAL(a.sumValue, 24.0); + EXPECT_EQUAL(a.minValue, 7.0); + EXPECT_EQUAL(a.maxValue, 9.0); + EXPECT_EQUAL(a.lastValue, 8.0); + + a.merge(b); + EXPECT_EQUAL(a.observedCount, 7u); + EXPECT_EQUAL(a.sumValue, 48.0); + EXPECT_EQUAL(a.minValue, 7.0); + EXPECT_EQUAL(a.maxValue, 9.0); + EXPECT_EQUAL(a.lastValue, 8.0); + + a.merge(c); + EXPECT_EQUAL(a.observedCount, 9u); + EXPECT_EQUAL(a.sumValue, 59.0); + EXPECT_EQUAL(a.minValue, 1.0); + EXPECT_EQUAL(a.maxValue, 10.0); + EXPECT_EQUAL(a.lastValue, 1.0); +} + +bool compare_json(const vespalib::string &a, const vespalib::string &b) +{ + using vespalib::Memory; + using vespalib::slime::JsonFormat; + + Slime slimeA, slimeB; + if (! JsonFormat::decode(a, slimeA)) { +fprintf(stderr, "bad json a:\n>>>%s\n<<<\n", a.c_str()); + return false; + } + if (! JsonFormat::decode(b, slimeB)) { +fprintf(stderr, "bad json b\n"); + return false; + } + return slimeA == slimeB; +} + +void check_json(const vespalib::string &actual) +{ + vespalib::string expect = "{" + " snapshot: { from: 1, to: 4 }," + " values: [ { name: 'foo'," + " values: { count: 17, rate: 4.85714 }" + " }, {" + " name: 'foo'," + " dimensions: { chain: 'default', documenttype: 'music', thread: '0' }," + " values: { count: 4, rate: 1.14286 }" + " }, {" + " name: 'bar'," + " values: { count: 4, rate: 1.14286, average: 42, min: 41, max: 43, last: 42 }" + " }, {" + " name: 'bar'," + " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '1' }," + " values: { count: 1, rate: 0.285714, average: 14, min: 14, max: 14, last: 14 }" + " }, {" + " name: 'bar'," + " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '2' }," + " values: { count: 1, rate: 0.285714, average: 11, min: 11, max: 11, last: 11 }" + " } ]" + "}"; + EXPECT_TRUE(compare_json(expect, actual)); +} + + +TEST("use simple_metrics_collector") +{ + using namespace vespalib::metrics; + SimpleManagerConfig cf; + cf.sliding_window_seconds = 5; + std::shared_ptr<MockTick> ticker = std::make_shared<MockTick>(TimeStamp(1.0)); + auto manager = SimpleMetricsManager::createForTest(cf, std::make_unique<TickProxy>(ticker)); + + Counter myCounter = manager->counter("foo", "no description"); + myCounter.add(); + myCounter.add(16); + + Gauge myGauge = manager->gauge("bar", "dummy description"); + myGauge.sample(42.0); + myGauge.sample(41.0); + myGauge.sample(43.0); + myGauge.sample(42.0); + + EXPECT_EQUAL(1.0, ticker->give(TimeStamp(2.0)).count()); + + Snapshot snap1 = manager->snapshot(); + EXPECT_EQUAL(1.0, snap1.startTime()); + EXPECT_EQUAL(2.0, snap1.endTime()); + + EXPECT_EQUAL(1u, snap1.counters().size()); + EXPECT_EQUAL("foo", snap1.counters()[0].name()); + EXPECT_EQUAL(17u, snap1.counters()[0].count()); + + EXPECT_EQUAL(1u, snap1.gauges().size()); + EXPECT_EQUAL("bar", snap1.gauges()[0].name()); + EXPECT_EQUAL(4u, snap1.gauges()[0].observedCount()); + EXPECT_EQUAL(41.0, snap1.gauges()[0].minValue()); + EXPECT_EQUAL(43.0, snap1.gauges()[0].maxValue()); + EXPECT_EQUAL(42.0, snap1.gauges()[0].lastValue()); + + Point one = manager->pointBuilder() + .bind("chain", "default") + .bind("documenttype", "music") + .bind("thread", "0").build(); + Point two = manager->pointBuilder() + .bind("chain", "vespa") + .bind("documenttype", "blogpost") + .bind("thread", "1"); + EXPECT_EQUAL(one.id(), 1u); + EXPECT_EQUAL(two.id(), 2u); + + Point anotherOne = manager->pointBuilder() + .bind("chain", "default") + .bind("documenttype", "music") + .bind("thread", "0"); + EXPECT_EQUAL(anotherOne.id(), 1u); + + Point three = manager->pointBuilder(two).bind("thread", "2"); + EXPECT_EQUAL(three.id(), 3u); + + myCounter.add(3, one); + myCounter.add(one); + myGauge.sample(14.0, two); + myGauge.sample(11.0, three); + + EXPECT_EQUAL(2.0, ticker->give(TimeStamp(4.5)).count()); + + Snapshot snap2 = manager->snapshot(); + EXPECT_EQUAL(1.0, snap2.startTime()); + EXPECT_EQUAL(4.5, snap2.endTime()); + EXPECT_EQUAL(2u, snap2.counters().size()); + EXPECT_EQUAL(3u, snap2.gauges().size()); + + JsonFormatter fmt2(snap2); + check_json(fmt2.asString()); + + // flush sliding window + for (int i = 5; i <= 10; ++i) { + ticker->give(TimeStamp(i)); + } + Snapshot snap3 = manager->snapshot(); + EXPECT_EQUAL(5.0, snap3.startTime()); + EXPECT_EQUAL(10.0, snap3.endTime()); + EXPECT_EQUAL(2u, snap3.counters().size()); + EXPECT_EQUAL(0u, snap3.counters()[0].count()); + EXPECT_EQUAL(0u, snap3.counters()[1].count()); + EXPECT_EQUAL(3u, snap3.gauges().size()); + EXPECT_EQUAL(0u, snap3.gauges()[0].observedCount()); + EXPECT_EQUAL(0u, snap3.gauges()[1].observedCount()); + EXPECT_EQUAL(0u, snap3.gauges()[2].observedCount()); + + Snapshot snap4 = manager->totalSnapshot(); + EXPECT_EQUAL(1.0, snap4.startTime()); + EXPECT_EQUAL(10.0, snap4.endTime()); + EXPECT_EQUAL(2u, snap4.counters().size()); + EXPECT_NOT_EQUAL(0u, snap4.counters()[0].count()); + EXPECT_NOT_EQUAL(0u, snap4.counters()[1].count()); + EXPECT_EQUAL(3u, snap4.gauges().size()); + EXPECT_NOT_EQUAL(0u, snap4.gauges()[0].observedCount()); + EXPECT_NOT_EQUAL(0u, snap4.gauges()[1].observedCount()); + EXPECT_NOT_EQUAL(0u, snap4.gauges()[2].observedCount()); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/staging_vespalib/src/tests/metrics/stable_store_test.cpp b/staging_vespalib/src/tests/metrics/stable_store_test.cpp new file mode 100644 index 00000000000..03b6663cd64 --- /dev/null +++ b/staging_vespalib/src/tests/metrics/stable_store_test.cpp @@ -0,0 +1,65 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/metrics/simple_metrics.h> +#include <vespa/vespalib/metrics/simple_metrics_manager.h> +#include <vespa/vespalib/metrics/stable_store.h> +#include <vespa/vespalib/metrics/json_formatter.h> +#include <stdio.h> +#include <unistd.h> + +using namespace vespalib; +using namespace vespalib::metrics; + +struct Foo { + int a; + char *p; + explicit Foo(int v) : a(v), p(nullptr) {} + bool operator==(const Foo &other) const { + return a == other.a; + } +}; + +TEST("require that stable_store works") +{ + vespalib::StableStore<Foo> bunch; + bunch.add(Foo(1)); + bunch.add(Foo(2)); + bunch.add(Foo(3)); + bunch.add(Foo(5)); + bunch.add(Foo(8)); + bunch.add(Foo(13)); + bunch.add(Foo(21)); + bunch.add(Foo(34)); + bunch.add(Foo(55)); + bunch.add(Foo(89)); + + EXPECT_EQUAL(bunch.size(), 10u); + + int sum = 0; + + bunch.for_each([&sum](const Foo& value) { sum += value.a; }); + EXPECT_EQUAL(231, sum); + + std::vector<const Foo *> pointers; + bunch.for_each([&pointers](const Foo& value) + { pointers.push_back(&value); }); + EXPECT_EQUAL(1, pointers[0]->a); + EXPECT_EQUAL(2, pointers[1]->a); + EXPECT_EQUAL(55, pointers[8]->a); + EXPECT_EQUAL(89, pointers[9]->a); + + for (int i = 0; i < 20000; ++i) { + bunch.add(Foo(i)); + } + bunch.for_each([&sum](const Foo& value) { sum -= value.a; }); + EXPECT_EQUAL(-199990000, sum); + + std::vector<const Foo *> after; + bunch.for_each([&after](const Foo& value) + { if (after.size() < 10) after.push_back(&value); }); + + EXPECT_EQUAL(pointers[0], after[0]); + EXPECT_EQUAL(pointers[9], after[9]); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/staging_vespalib/src/vespa/vespalib/CMakeLists.txt b/staging_vespalib/src/vespa/vespalib/CMakeLists.txt index 24d576eb775..76c69284839 100644 --- a/staging_vespalib/src/vespa/vespalib/CMakeLists.txt +++ b/staging_vespalib/src/vespa/vespalib/CMakeLists.txt @@ -4,6 +4,7 @@ vespa_add_library(staging_vespalib $<TARGET_OBJECTS:staging_vespalib_vespalib_encoding> $<TARGET_OBJECTS:staging_vespalib_vespalib_util> $<TARGET_OBJECTS:staging_vespalib_vespalib_data> + $<TARGET_OBJECTS:staging_vespalib_vespalib_metrics> $<TARGET_OBJECTS:staging_vespalib_vespalib_objects> $<TARGET_OBJECTS:staging_vespalib_vespalib_stllike> $<TARGET_OBJECTS:staging_vespalib_vespalib_net> diff --git a/staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt b/staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt new file mode 100644 index 00000000000..34cc5453304 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_library(staging_vespalib_vespalib_metrics OBJECT + SOURCES + bucket.cpp + clock.cpp + counter_aggregator.cpp + counter.cpp + current_samples.cpp + dimension.cpp + dummy_metrics_manager.cpp + gauge_aggregator.cpp + gauge.cpp + handle.cpp + json_formatter.cpp + label.cpp + metric_identifier.cpp + metric_name.cpp + metrics_manager.cpp + metric_types.cpp + name_collection.cpp + point_builder.cpp + point.cpp + point_map_collection.cpp + point_map.cpp + producer.cpp + simple_metrics.cpp + simple_metrics_manager.cpp + simple_tick.cpp + snapshots.cpp + stable_store.cpp + + DEPENDS +) diff --git a/staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp b/staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp new file mode 100644 index 00000000000..52e95e1ac3f --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/bucket.cpp @@ -0,0 +1,143 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "bucket.h" +#include <assert.h> +#include <map> + +namespace vespalib { +namespace metrics { + +namespace { + +template<typename T> +std::vector<typename T::aggregator_type> +mergeFromSamples(const StableStore<typename T::sample_type> &source) +{ + using Aggregator = typename T::aggregator_type; + using Sample = typename T::sample_type; + using Map = std::map<MetricIdentifier, Aggregator>; + using MapValue = typename Map::value_type; + + Map map; + source.for_each([&map] (const Sample &sample) { + MetricIdentifier id = sample.idx; + auto iter_check = map.emplace(id, sample); + if (!iter_check.second) { + iter_check.first->second.merge(sample); + } + }); + std::vector<typename T::aggregator_type> result; + for (const MapValue &entry : map) { + result.push_back(entry.second); + } + return result; +} + +template<typename T> +std::vector<T> +mergeVectors(const std::vector<T> &a, + const std::vector<T> &b) +{ + std::vector<T> result; + auto a_iter = a.begin(); + auto b_iter = b.begin(); + while (a_iter != a.end() && + b_iter != b.end()) + { + if (a_iter->idx < b_iter->idx) { + result.push_back(*a_iter); + ++a_iter; + } else if (b_iter->idx < a_iter->idx) { + result.push_back(*b_iter); + ++b_iter; + } else { + result.push_back(*a_iter); + result.back().merge(*b_iter); + ++a_iter; + ++b_iter; + } + } + while (a_iter != a.end()) { + result.push_back(*a_iter); + ++a_iter; + } + while (b_iter != b.end()) { + result.push_back(*b_iter); + ++b_iter; + } + return result; +} + +template<typename T> +std::vector<T> +findMissing(const std::vector<T> &already, + const std::vector<T> &complete) +{ + std::vector<T> result; + auto a_iter = already.begin(); + auto c_iter = complete.begin(); + while (a_iter != already.end() && + c_iter != complete.end()) + { + if (a_iter->idx < c_iter->idx) { + // missing from "complete", should not happen + ++a_iter; + } else if (c_iter->idx < a_iter->idx) { + // missing this + result.push_back(*c_iter); + ++c_iter; + } else { + // already have this + ++a_iter; + ++c_iter; + } + } + while (c_iter != complete.end()) { + // missing this + result.push_back(*c_iter); + ++c_iter; + } + return result; +} + + +} // namespace <unnamed> + +void Bucket::merge(const CurrentSamples &samples) +{ + counters = mergeFromSamples<Counter>(samples.counterIncrements); + gauges = mergeFromSamples<Gauge>(samples.gaugeMeasurements); +} + +void Bucket::merge(const Bucket &other) +{ + assert(genCnt < other.genCnt); + genCnt = other.genCnt; + startTime = std::min(startTime, other.startTime); + endTime = std::max(endTime, other.endTime); + + std::vector<CounterAggregator> nextCounters = mergeVectors(counters, other.counters); + counters = std::move(nextCounters); + + std::vector<GaugeAggregator> nextGauges = mergeVectors(gauges, other.gauges); + gauges = std::move(nextGauges); +} + +void Bucket::padMetrics(const Bucket &source) +{ + std::vector<CounterAggregator> missingC = findMissing(counters, source.counters); + for (CounterAggregator aggr : missingC) { + aggr.count = 0; + counters.push_back(aggr); + } + std::vector<GaugeAggregator> missingG = findMissing(gauges, source.gauges); + for (GaugeAggregator aggr : missingG) { + aggr.observedCount = 0; + aggr.sumValue = 0; + aggr.minValue = 0; + aggr.maxValue = 0; + gauges.push_back(aggr); + } +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/bucket.h b/staging_vespalib/src/vespa/vespalib/metrics/bucket.h new file mode 100644 index 00000000000..5a88e435502 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/bucket.h @@ -0,0 +1,43 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <mutex> +#include "stable_store.h" +#include "metric_identifier.h" +#include "counter.h" +#include "gauge.h" +#include "clock.h" +#include "counter_aggregator.h" +#include "gauge_aggregator.h" +#include "current_samples.h" + +namespace vespalib { +namespace metrics { + +// internal +struct Bucket { + size_t genCnt; + TimeStamp startTime; + TimeStamp endTime; + std::vector<CounterAggregator> counters; + std::vector<GaugeAggregator> gauges; + + void merge(const CurrentSamples &other); + void merge(const Bucket &other); + void padMetrics(const Bucket &source); + + Bucket(size_t generation, TimeStamp started, TimeStamp ended) + : genCnt(generation), + startTime(started), + endTime(ended), + counters(), + gauges() + {} + ~Bucket() {} + Bucket(Bucket &&) = default; + Bucket(const Bucket &) = default; + Bucket& operator= (Bucket &&) = default; +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/clock.cpp b/staging_vespalib/src/vespa/vespalib/metrics/clock.cpp new file mode 100644 index 00000000000..82144da895d --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/clock.cpp @@ -0,0 +1,2 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "clock.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/clock.h b/staging_vespalib/src/vespa/vespalib/metrics/clock.h new file mode 100644 index 00000000000..2af2ad89427 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/clock.h @@ -0,0 +1,30 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <chrono> +#include <memory> + +namespace vespalib::metrics { + +using TimeStamp = std::chrono::duration<double, std::ratio<1,1>>; + +/** + * Simple interface abstracting both timing and time measurement for + * threads wanting to do stuff at regular intervals and also knowing + * at what time stuff was done. The 'next' function blocks until the + * next tick is due and returns the current number of seconds since + * epoch. The parameter passed to the 'next' function should be its + * previous return value, except the first time it is called, then 0 + * should be used. A convenience function called 'first' is added for + * this purpose. + **/ +struct Tick { + using UP = std::unique_ptr<Tick>; + virtual TimeStamp next(TimeStamp prev) = 0; + virtual TimeStamp first() = 0; + virtual void kill() = 0; + virtual bool alive() const = 0; + virtual ~Tick() {} +}; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter.cpp b/staging_vespalib/src/vespa/vespalib/metrics/counter.cpp new file mode 100644 index 00000000000..08f72e6aa34 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/counter.cpp @@ -0,0 +1,19 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "counter.h" +#include "metrics_manager.h" + +namespace vespalib { +namespace metrics { + + +void +Counter::add(size_t count, Point point) const +{ + if (_manager) { + MetricIdentifier fullId(_id, point); + _manager->add(Increment(fullId, count)); + } +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter.h b/staging_vespalib/src/vespa/vespalib/metrics/counter.h new file mode 100644 index 00000000000..36a25adda2d --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/counter.h @@ -0,0 +1,56 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <memory> +#include "metric_identifier.h" +#include "point.h" + +namespace vespalib { +namespace metrics { + +class MetricsManager; +class CounterAggregator; + + +/** + * Represents a counter metric that can be incremented. + **/ +class Counter { + std::shared_ptr<MetricsManager> _manager; + MetricName _id; +public: + Counter() : _manager(), _id(0) {} + Counter(const Counter&) = delete; + Counter(Counter &&other) = default; + Counter& operator= (const Counter &) = delete; + Counter& operator= (Counter &&other) = default; + Counter(std::shared_ptr<MetricsManager> m, MetricName id) + : _manager(std::move(m)), _id(id) + {} + + // convenience methods: + void add() const { add(1, Point::empty); } + void add(Point p) { add(1, p); } + void add(size_t count) const { add(count, Point::empty); } + + /** + * Increment the counter. + * @param count the amount to increment by (default 1) + * @param p the point representing labels for this increment (default empty) + **/ + void add(size_t count, Point p) const; + + // internal + struct Increment { + MetricIdentifier idx; + size_t value; + Increment() = delete; + Increment(MetricIdentifier id, size_t v) : idx(id), value(v) {} + }; + + typedef CounterAggregator aggregator_type; + typedef Increment sample_type; +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp new file mode 100644 index 00000000000..a02cc1e7a7e --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.cpp @@ -0,0 +1,21 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "counter_aggregator.h" +#include <assert.h> +#include <map> + +namespace vespalib { +namespace metrics { + +CounterAggregator::CounterAggregator(const Counter::Increment &increment) + : idx(increment.idx), count(increment.value) +{} + +void +CounterAggregator::merge(const CounterAggregator &other) +{ + assert(idx == other.idx); + count += other.count; +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h new file mode 100644 index 00000000000..995ba07deb3 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/counter_aggregator.h @@ -0,0 +1,20 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "metric_identifier.h" +#include "counter.h" + +namespace vespalib { +namespace metrics { + +// internal +struct CounterAggregator { + MetricIdentifier idx; + size_t count; + + CounterAggregator(const Counter::Increment &other); + void merge(const CounterAggregator &other); +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp new file mode 100644 index 00000000000..55211717973 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.cpp @@ -0,0 +1,32 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "current_samples.h" + +namespace vespalib { +namespace metrics { + +using Guard = std::lock_guard<std::mutex>; + +void +CurrentSamples::add(Counter::Increment inc) +{ + Guard guard(lock); + counterIncrements.add(inc); +} + +void +CurrentSamples::sample(Gauge::Measurement value) +{ + Guard guard(lock); + gaugeMeasurements.add(value); +} + +void +CurrentSamples::extract(CurrentSamples &into) +{ + Guard guard(lock); + swap(into.counterIncrements, counterIncrements); + swap(into.gaugeMeasurements, gaugeMeasurements); +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/current_samples.h b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.h new file mode 100644 index 00000000000..1b87b135aa2 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/current_samples.h @@ -0,0 +1,26 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <mutex> +#include "stable_store.h" +#include "counter.h" +#include "gauge.h" + +namespace vespalib { +namespace metrics { + +// internal +struct CurrentSamples { + std::mutex lock; + StableStore<Counter::Increment> counterIncrements; + StableStore<Gauge::Measurement> gaugeMeasurements; + + ~CurrentSamples() {} + + void add(Counter::Increment inc); + void sample(Gauge::Measurement value); + void extract(CurrentSamples &into); +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp b/staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp new file mode 100644 index 00000000000..05bdd42f34a --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/dimension.cpp @@ -0,0 +1,2 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "dimension.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dimension.h b/staging_vespalib/src/vespa/vespalib/metrics/dimension.h new file mode 100644 index 00000000000..ee16bc6f98a --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/dimension.h @@ -0,0 +1,18 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/stllike/string.h> +#include "handle.h" + +namespace vespalib::metrics { + +using DimensionName = vespalib::string; + +struct DimensionTag {}; + +/** + * Opaque handle representing an uniquely named dimension. + **/ +using Dimension = Handle<DimensionTag>; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp new file mode 100644 index 00000000000..cd7a3abb1eb --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.cpp @@ -0,0 +1,24 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "dummy_metrics_manager.h" + +namespace vespalib { +namespace metrics { + +DummyMetricsManager::~DummyMetricsManager() {} + +Snapshot +DummyMetricsManager::snapshot() +{ + Snapshot snap(0, 0); + return snap; +} + +Snapshot +DummyMetricsManager::totalSnapshot() +{ + Snapshot snap(0, 0); + return snap; +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h new file mode 100644 index 00000000000..52e9c6a608c --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/dummy_metrics_manager.h @@ -0,0 +1,62 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <memory> +#include <thread> +#include <vespa/vespalib/stllike/string.h> +#include "name_collection.h" +#include "current_samples.h" +#include "snapshots.h" +#include "metrics_manager.h" +#include "clock.h" + +namespace vespalib { +namespace metrics { + +/** + * Dummy manager that discards everything, use + * for unit tests where you don't care about + * metrics. + **/ +class DummyMetricsManager : public MetricsManager +{ +private: + DummyMetricsManager() {} +public: + ~DummyMetricsManager(); + + static std::shared_ptr<MetricsManager> create() { + return std::shared_ptr<MetricsManager>(new DummyMetricsManager()); + } + + Counter counter(const vespalib::string &, const vespalib::string &) override { + return Counter(shared_from_this(), MetricName(0)); + } + Gauge gauge(const vespalib::string &, const vespalib::string &) override { + return Gauge(shared_from_this(), MetricName(0)); + } + + Dimension dimension(const vespalib::string &) override { + return Dimension(0); + } + Label label(const vespalib::string &) override { + return Label(0); + } + PointBuilder pointBuilder(Point) override { + return PointBuilder(shared_from_this()); + } + Point pointFrom(PointMap::BackingMap) override { + return Point(0); + } + + Snapshot snapshot() override; + Snapshot totalSnapshot() override; + + // for use from Counter only + void add(Counter::Increment) override {} + // for use from Gauge only + void sample(Gauge::Measurement) override {} +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp b/staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp new file mode 100644 index 00000000000..af98ba2de18 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge.cpp @@ -0,0 +1,18 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "gauge.h" +#include "metrics_manager.h" + +namespace vespalib { +namespace metrics { + +void +Gauge::sample(double value, Point point) const +{ + if (_manager) { + MetricIdentifier fullId(_id, point); + _manager->sample(Measurement(fullId, value)); + } +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge.h b/staging_vespalib/src/vespa/vespalib/metrics/gauge.h new file mode 100644 index 00000000000..fa3f826f2b8 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge.h @@ -0,0 +1,46 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <memory> +#include "metric_identifier.h" +#include "point.h" + +namespace vespalib { +namespace metrics { + +class MetricsManager; +class GaugeAggregator; + +/** + * Represents a gauge metric that can be measured. + **/ +class Gauge { +private: + std::shared_ptr<MetricsManager> _manager; + MetricName _id; +public: + Gauge(std::shared_ptr<MetricsManager> m, MetricName id) + : _manager(std::move(m)), _id(id) + {} + + /** + * Provide a sample for the gauge. + * @param value the measurement for this sample + * @param p the point representing labels for this sample (default empty) + **/ + void sample(double value, Point p = Point::empty) const; + + // internal + struct Measurement { + MetricIdentifier idx; + double value; + Measurement() = delete; + Measurement(MetricIdentifier id, double v) : idx(id), value(v) {} + }; + + typedef GaugeAggregator aggregator_type; + typedef Measurement sample_type; +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp new file mode 100644 index 00000000000..84ce8aef28d --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.cpp @@ -0,0 +1,30 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "gauge_aggregator.h" +#include <assert.h> +#include <map> + +namespace vespalib { +namespace metrics { + +GaugeAggregator::GaugeAggregator(const Gauge::Measurement &sample) + : idx(sample.idx), + observedCount(1), + sumValue(sample.value), + minValue(sample.value), + maxValue(sample.value), + lastValue(sample.value) +{} + +void +GaugeAggregator::merge(const GaugeAggregator &other) +{ + assert(idx == other.idx); + minValue = std::min(minValue, other.minValue); + maxValue = std::max(maxValue, other.maxValue); + sumValue += other.sumValue; + lastValue = other.lastValue; + observedCount += other.observedCount; +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h new file mode 100644 index 00000000000..84772d7fff4 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/gauge_aggregator.h @@ -0,0 +1,24 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "metric_identifier.h" +#include "gauge.h" + +namespace vespalib { +namespace metrics { + +// internal +struct GaugeAggregator { + MetricIdentifier idx; + size_t observedCount; + double sumValue; + double minValue; + double maxValue; + double lastValue; + + GaugeAggregator(const Gauge::Measurement &other); + void merge(const GaugeAggregator &other); +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/handle.cpp b/staging_vespalib/src/vespa/vespalib/metrics/handle.cpp new file mode 100644 index 00000000000..2b806caeaea --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/handle.cpp @@ -0,0 +1,2 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "handle.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/handle.h b/staging_vespalib/src/vespa/vespalib/metrics/handle.h new file mode 100644 index 00000000000..94a9ee33830 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/handle.h @@ -0,0 +1,52 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <cstddef> + +namespace vespalib { +namespace metrics { + +/** + * Common implementation of an opaque handle identified only + * by a (64-bit) integer. Templated to avoid different concepts + * sharing a superclass. + **/ +template <typename T> +class Handle { +private: + const size_t _id; +public: + explicit Handle(size_t id) : _id(id) {} + size_t id() const { return _id; } +}; + +template <typename T> +bool +operator< (const Handle<T> &a, const Handle<T> &b) noexcept +{ + return a.id() < b.id(); +} + +template <typename T> +bool +operator> (const Handle<T> &a, const Handle<T> &b) noexcept +{ + return a.id() > b.id(); +} + +template <typename T> +bool +operator== (const Handle<T> &a, const Handle<T> &b) noexcept +{ + return a.id() == b.id(); +} + +template <typename T> +bool +operator!= (const Handle<T> &a, const Handle<T> &b) noexcept +{ + return a.id() != b.id(); +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp new file mode 100644 index 00000000000..280211bd780 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.cpp @@ -0,0 +1,75 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "json_formatter.h" + +namespace vespalib { +namespace metrics { + +JsonFormatter::JsonFormatter(const Snapshot &snapshot) + : _data(), + _top(_data.setObject()), + _snapLen(snapshot.endTime() - snapshot.startTime()) +{ + if (_snapLen < 0.1) { + _snapLen = 0.1; + } + // cosmetics: ordering inside objects + _data.insert("name"); + _data.insert("dimensions"); + vespalib::slime::Cursor& meta = _top.setObject("snapshot"); + meta.setLong("from", (long)snapshot.startTime()); + meta.setLong("to", (long)snapshot.endTime()); + handle(snapshot, _top.setArray("values")); +} + +void +JsonFormatter::handle(const Snapshot &snapshot, vespalib::slime::Cursor &target) +{ + for (const CounterSnapshot &entry : snapshot.counters()) { + handle(entry, target.addObject()); + } + for (const GaugeSnapshot &entry : snapshot.gauges()) { + handle(entry, target.addObject()); + } +} + +void +JsonFormatter::handle(const CounterSnapshot &snapshot, vespalib::slime::Cursor &target) +{ + target.setString("name", snapshot.name()); + // target.setString("description", ?); + handle(snapshot.point(), target); + Cursor& inner = target.setObject("values"); + inner.setLong("count", snapshot.count()); + inner.setDouble("rate", snapshot.count() / _snapLen); +} + +void +JsonFormatter::handle(const GaugeSnapshot &snapshot, vespalib::slime::Cursor &target) +{ + target.setString("name", snapshot.name()); + // target.setString("description", ?); + handle(snapshot.point(), target); + Cursor& inner = target.setObject("values"); + inner.setDouble("average", snapshot.averageValue()); + inner.setDouble("min", snapshot.minValue()); + inner.setDouble("max", snapshot.maxValue()); + inner.setDouble("last", snapshot.lastValue()); + inner.setLong("count", snapshot.observedCount()); + inner.setDouble("rate", snapshot.observedCount() / _snapLen); +} + +void +JsonFormatter::handle(const PointSnapshot &snapshot, vespalib::slime::Cursor &target) +{ + if (snapshot.dimensions.size() == 0) { + return; + } + Cursor& inner = target.setObject("dimensions"); + for (const DimensionBinding &entry : snapshot.dimensions) { + inner.setString(entry.dimensionName(), entry.labelValue()); + } +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h new file mode 100644 index 00000000000..672b71766ae --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/json_formatter.h @@ -0,0 +1,37 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "snapshots.h" +#include <vespa/vespalib/stllike/string.h> +#include <vespa/vespalib/data/slime/slime.h> + +namespace vespalib { +namespace metrics { + +/** + * utility for converting a snapshot to JSON format + * (which can be inserted into /state/v1/metrics page). + **/ +class JsonFormatter +{ +private: + using Cursor = vespalib::slime::Cursor; + vespalib::Slime _data; + Cursor& _top; + double _snapLen; + + void handle(const Snapshot &snapshot, Cursor &target); + void handle(const PointSnapshot &snapshot, Cursor &target); + void handle(const CounterSnapshot &snapshot, Cursor &target); + void handle(const GaugeSnapshot &snapshot, Cursor &target); +public: + JsonFormatter(const Snapshot &snapshot); + + vespalib::string asString() const { + return _data.toString(); + } +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/label.cpp b/staging_vespalib/src/vespa/vespalib/metrics/label.cpp new file mode 100644 index 00000000000..218db1ca2ce --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/label.cpp @@ -0,0 +1,2 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "label.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/label.h b/staging_vespalib/src/vespa/vespalib/metrics/label.h new file mode 100644 index 00000000000..e2356415fd6 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/label.h @@ -0,0 +1,18 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/stllike/string.h> +#include "handle.h" + +namespace vespalib::metrics { + +using LabelValue = vespalib::string; + +struct LabelTag {}; + +/** + * Opaque handle representing an uniquely named label. + **/ +using Label = Handle<LabelTag>; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp new file mode 100644 index 00000000000..950adc3462d --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.cpp @@ -0,0 +1,2 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "metric_identifier.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h new file mode 100644 index 00000000000..0fc5af41a81 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_identifier.h @@ -0,0 +1,38 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "metric_name.h" +#include "point.h" +#include <functional> + +namespace vespalib { +namespace metrics { + +// internal +struct MetricIdentifier { + const MetricName _name; + const Point _point; + + MetricIdentifier() = delete; + + MetricIdentifier(MetricName name, Point point) + : _name(name), _point(point) {} + + bool operator< (const MetricIdentifier &other) const { + if (_name != other._name) { + return _name < other._name; + } + return _point < other._point; + } + bool operator== (const MetricIdentifier &other) const { + return (_name == other._name && + _point == other._point); + } + + MetricName name() const { return _name; } + Point point() const { return _point; } + +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp new file mode 100644 index 00000000000..2a58d55e945 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.cpp @@ -0,0 +1,2 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "metric_name.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_name.h b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.h new file mode 100644 index 00000000000..60dfa0aa00f --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_name.h @@ -0,0 +1,15 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "handle.h" + +namespace vespalib::metrics { + +struct MetricNameTag {}; + +/** + * Opaque handle representing an uniquely named metric. + **/ +using MetricName = Handle<MetricNameTag>; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp new file mode 100644 index 00000000000..1d8b1fe2a01 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.cpp @@ -0,0 +1,44 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "metric_types.h" +#include <assert.h> + +#include <vespa/log/log.h> +LOG_SETUP(".vespalib.metrics.metric_types"); + +namespace vespalib { +namespace metrics { + +const char* MetricTypes::_typeNames[] = { + "INVALID", + "Counter", + "Gauge", + "Histogram", + "IntegerHistogram" +}; + +void +MetricTypes::check(size_t id, const vespalib::string &name, MetricType ty) +{ + std::lock_guard<std::mutex> guard(_lock); + if (id < _seen.size()) { + MetricType old = _seen[id]; + if (old == ty) { + return; + } + if (old == MetricType::INVALID) { + _seen[id] = ty; + } + LOG(warning, "metric '%s' with different types %s and %s, this will be confusing", + name.c_str(), _typeNames[ty], _typeNames[old]); + } + while (_seen.size() < id) { + _seen.push_back(MetricType::INVALID); + } + _seen.push_back(ty); +} + +} // namespace vespalib::metrics +} // namespace vespalib + + + diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metric_types.h b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.h new file mode 100644 index 00000000000..5c764dcc74e --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metric_types.h @@ -0,0 +1,33 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <mutex> +#include <vector> +#include <vespa/vespalib/stllike/string.h> + +namespace vespalib { +namespace metrics { + +// internal class for typechecking +class MetricTypes { + static const char *_typeNames[]; +public: + enum MetricType { + INVALID, + COUNTER, + GAUGE, + HISTOGRAM, + INT_HISTOGRAM + }; + + void check(size_t id, const vespalib::string& name, MetricType ty); + + MetricTypes() = default; + ~MetricTypes() {} +private: + std::mutex _lock; + std::vector<MetricType> _seen; +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp new file mode 100644 index 00000000000..be6612bd869 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.cpp @@ -0,0 +1,8 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "metrics_manager.h" + +namespace vespalib { +namespace metrics { + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h new file mode 100644 index 00000000000..14f11fa4ab2 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/metrics_manager.h @@ -0,0 +1,91 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <memory> +#include <thread> +#include <vespa/vespalib/stllike/string.h> +#include "name_collection.h" +#include "counter.h" +#include "gauge.h" +#include "current_samples.h" +#include "snapshots.h" +#include "point.h" +#include "point_builder.h" +#include "dimension.h" +#include "label.h" + +namespace vespalib::metrics { + + +/** + * Interface for a Metrics manager, for creating metrics + * and for fetching snapshots. + **/ +class MetricsManager + : public std::enable_shared_from_this<MetricsManager> +{ +public: + virtual ~MetricsManager() {} + + /** + * Get or create a counter metric. + * @param name the name of the metric. + **/ + virtual Counter counter(const vespalib::string &name, const vespalib::string &description) = 0; + + /** + * Get or create a gauge metric. + * @param name the name of the metric. + **/ + virtual Gauge gauge(const vespalib::string &name, const vespalib::string &description) = 0; + + /** + * Get or create a dimension for labeling metrics. + * @param name the name of the dimension. + **/ + virtual Dimension dimension(const vespalib::string &name) = 0; // get or create + + /** + * Get or create a label. + * @param value the label value. + **/ + virtual Label label(const vespalib::string &value) = 0; // get or create + + /** + * Create a PointBuilder for labeling metrics. + **/ + PointBuilder pointBuilder() { + return PointBuilder(shared_from_this()); + } + + /** + * Create a PointBuilder for labeling metrics, starting with + * an Point of already existing dimension/label pairs, which can + * then be added to or changed. + * @param from provide a Point to start from. + * + **/ + virtual PointBuilder pointBuilder(Point from) = 0; + + /** + * Create a snapshot of sampled metrics (usually for the last minute). + **/ + virtual Snapshot snapshot() = 0; + + /** + * Create a snapshot of all sampled metrics the manager has seen. + **/ + virtual Snapshot totalSnapshot() = 0; + + // for use from PointBuilder only + virtual Point pointFrom(PointMap::BackingMap map) = 0; + + // for use from Counter only + virtual void add(Counter::Increment inc) = 0; + + // for use from Gauge only + virtual void sample(Gauge::Measurement value) = 0; +}; + + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp new file mode 100644 index 00000000000..697d41c4c6b --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.cpp @@ -0,0 +1,38 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "name_collection.h" +#include <assert.h> + +namespace vespalib { +namespace metrics { + +using Guard = std::lock_guard<std::mutex>; + +const vespalib::string & +NameCollection::lookup(size_t id) const +{ + Guard guard(_lock); + assert(id < _names_by_id.size()); + return _names_by_id[id]->first; +} + +size_t +NameCollection::resolve(const vespalib::string& name) +{ + Guard guard(_lock); + size_t nextId = _names_by_id.size(); + auto iter_check = _names.emplace(name, nextId); + if (iter_check.second) { + _names_by_id.push_back(iter_check.first); + } + return iter_check.first->second; +} + +size_t +NameCollection::size() const +{ + Guard guard(_lock); + return _names_by_id.size(); +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/name_collection.h b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.h new file mode 100644 index 00000000000..566fd2a3997 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/name_collection.h @@ -0,0 +1,29 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <mutex> +#include <map> +#include <vector> +#include <vespa/vespalib/stllike/string.h> + +namespace vespalib { +namespace metrics { + +// internal +class NameCollection { +private: + using Map = std::map<vespalib::string, size_t>; + mutable std::mutex _lock; + Map _names; + std::vector<Map::const_iterator> _names_by_id; +public: + const vespalib::string &lookup(size_t id) const; + size_t resolve(const vespalib::string& name); + size_t size() const; + + NameCollection() = default; + ~NameCollection() {} +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point.cpp new file mode 100644 index 00000000000..a5f99a4ba6d --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point.cpp @@ -0,0 +1,10 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "point.h" + +namespace vespalib { +namespace metrics { + +Point Point::empty(0); + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point.h b/staging_vespalib/src/vespa/vespalib/metrics/point.h new file mode 100644 index 00000000000..e50283bd94c --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point.h @@ -0,0 +1,17 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "handle.h" + +namespace vespalib::metrics { + +/** + * Opaque handle representing an unique N-dimensional point + **/ +class Point : public Handle<Point> { +public: + static Point empty; + explicit Point(size_t id) : Handle<Point>(id) {} +}; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp new file mode 100644 index 00000000000..7ee4ca35d0e --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.cpp @@ -0,0 +1,52 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "point.h" +#include "metrics_manager.h" + +namespace vespalib { +namespace metrics { + +PointBuilder::PointBuilder(std::shared_ptr<MetricsManager> m) + : _owner(std::move(m)), _map() +{} + +PointBuilder::PointBuilder(std::shared_ptr<MetricsManager> m, + const PointMap::BackingMap ©From) + : _owner(std::move(m)), _map(copyFrom) +{} + +PointBuilder && +PointBuilder::bind(Dimension dimension, Label label) && +{ + _map.erase(dimension); + _map.emplace(dimension, label); + return std::move(*this); +} + +PointBuilder && +PointBuilder::bind(Dimension dimension, LabelValue label) && +{ + Label c = _owner->label(label); + return std::move(*this).bind(dimension, c); +} + +PointBuilder && +PointBuilder::bind(DimensionName dimension, LabelValue label) && +{ + Dimension a = _owner->dimension(dimension); + Label c = _owner->label(label); + return std::move(*this).bind(a, c); +} + +Point +PointBuilder::build() +{ + return _owner->pointFrom(PointMap::BackingMap(_map)); +} + +PointBuilder::operator Point() && +{ + return _owner->pointFrom(std::move(_map)); +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_builder.h b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.h new file mode 100644 index 00000000000..83b804228dd --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point_builder.h @@ -0,0 +1,55 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <memory> +#include <vespa/vespalib/stllike/string.h> + +#include "point.h" +#include "point_map.h" + +namespace vespalib { +namespace metrics { + +class MetricsManager; + +/** + * Build a Point for labeling metrics + **/ +class PointBuilder { +private: + std::shared_ptr<MetricsManager> _owner; + PointMap::BackingMap _map; + +public: + // for use from MetricsManager + PointBuilder(std::shared_ptr<MetricsManager> m); + PointBuilder(std::shared_ptr<MetricsManager> m, const PointMap::BackingMap &from); + ~PointBuilder() {} + + /** + * Bind a dimension to a label. + * Overwrites any label already bound to that dimension. + **/ + PointBuilder &&bind(Dimension dimension, Label label) &&; + + /** + * Bind a dimension to a label. + * Convenience method that converts the label value. + **/ + PointBuilder &&bind(Dimension dimension, LabelValue label) &&; + + /** + * Bind a dimension to a label. + * Convenience method that converts both the dimension name and the label value. + **/ + PointBuilder &&bind(DimensionName dimension, LabelValue label) &&; + + /** make a Point from the builder */ + Point build(); + + /** make a Point from the builder */ + operator Point () &&; +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp new file mode 100644 index 00000000000..51908ae235e --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map.cpp @@ -0,0 +1,49 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "point.h" +#include "metrics_manager.h" + +namespace vespalib { +namespace metrics { + +PointMap::PointMap(BackingMap &&from) + : _map(std::move(from)), + _hash(0) +{ + for (const BackingMap::value_type &entry : _map) { + _hash = (_hash << 7) + (_hash >> 31) + entry.first.id(); + _hash = (_hash << 7) + (_hash >> 31) + entry.second.id(); + } +} + +bool +PointMap::operator< (const PointMap &other) const +{ + // cheap comparison first + if (_hash != other._hash) { + return _hash < other._hash; + } + if (_map.size() != other._map.size()) { + return _map.size() < other._map.size(); + } + // sizes equal, iterate in parallel + for (auto m = _map.begin(), o = other._map.begin(); + m != _map.end(); + ++m, ++o) + { + const Dimension& d1 = m->first; + const Dimension& d2 = o->first; + if (d1 != d2) { + return d1 < d2; + } + const Label &l1 = m->second; + const Label &l2 = o->second; + if (l1 != l2) { + return l1 < l2; + } + } + // equal + return false; +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map.h b/staging_vespalib/src/vespa/vespalib/metrics/point_map.h new file mode 100644 index 00000000000..2ed50a85842 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map.h @@ -0,0 +1,27 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <map> +#include "dimension.h" +#include "label.h" + +namespace vespalib { +namespace metrics { + +// internal +class PointMap { +public: + using BackingMap = std::map<Dimension, Label>; +private: + const PointMap::BackingMap _map; + size_t _hash; +public: + PointMap() : _map(), _hash(0) {} + PointMap(BackingMap &&from); + bool operator< (const PointMap &other) const; + + const BackingMap &backingMap() const { return _map; } +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp new file mode 100644 index 00000000000..7b09fbf7746 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.cpp @@ -0,0 +1,39 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "point_map_collection.h" +#include <assert.h> + +namespace vespalib { +namespace metrics { + +using Guard = std::lock_guard<std::mutex>; + +const PointMap & +PointMapCollection::lookup(size_t id) +{ + Guard guard(_lock); + assert(id < _vec.size()); + PointMapMap::const_iterator iter = _vec[id]; + return iter->first; +} + +size_t +PointMapCollection::resolve(PointMap map) +{ + Guard guard(_lock); + size_t nextId = _vec.size(); + auto iter_check = _map.emplace(std::move(map), nextId); + if (iter_check.second) { + _vec.push_back(iter_check.first); + } + return iter_check.first->second; +} + +size_t +PointMapCollection::size() const +{ + Guard guard(_lock); + return _vec.size(); +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h new file mode 100644 index 00000000000..ba301ff3f06 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/point_map_collection.h @@ -0,0 +1,30 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <mutex> +#include <map> +#include <vector> +#include "point_map.h" + +namespace vespalib { +namespace metrics { + +// internal +class PointMapCollection { +private: + using PointMapMap = std::map<PointMap, size_t>; + + mutable std::mutex _lock; + PointMapMap _map; + std::vector<PointMapMap::const_iterator> _vec; +public: + const PointMap &lookup(size_t id); + size_t resolve(PointMap map); + size_t size() const; + + PointMapCollection() = default; + ~PointMapCollection() {} +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/producer.cpp b/staging_vespalib/src/vespa/vespalib/metrics/producer.cpp new file mode 100644 index 00000000000..ae8d6d76eac --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/producer.cpp @@ -0,0 +1,33 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "producer.h" +#include "metrics_manager.h" +#include "json_formatter.h" + +namespace vespalib { +namespace metrics { + +Producer::Producer(std::shared_ptr<MetricsManager> m) + : _manager(m) +{} + +vespalib::string +Producer::getMetrics(const vespalib::string &) +{ + Snapshot snap = _manager->snapshot(); + JsonFormatter fmt(snap); + return fmt.asString(); +} + +vespalib::string +Producer::getTotalMetrics(const vespalib::string &) +{ + Snapshot snap = _manager->totalSnapshot(); + JsonFormatter fmt(snap); + return fmt.asString(); +} + + + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/producer.h b/staging_vespalib/src/vespa/vespalib/metrics/producer.h new file mode 100644 index 00000000000..387d94379b7 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/producer.h @@ -0,0 +1,25 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <memory> +#include <vespa/vespalib/net/metrics_producer.h> + +namespace vespalib { +namespace metrics { + +class MetricsManager; + +/** + * Utility class for wiring a MetricsManager into a StateApi. + **/ +class Producer : public vespalib::MetricsProducer { +private: + std::shared_ptr<MetricsManager> _manager; +public: + Producer(std::shared_ptr<MetricsManager> m); + vespalib::string getMetrics(const vespalib::string &consumer) override; + vespalib::string getTotalMetrics(const vespalib::string &consumer) override; +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp new file mode 100644 index 00000000000..d9bd3f00892 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.cpp @@ -0,0 +1,8 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "simple_metrics.h" + +namespace vespalib { +namespace metrics { + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h new file mode 100644 index 00000000000..6e6cff55b1f --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics.h @@ -0,0 +1,23 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vector> +#include <chrono> +#include <memory> +#include <vespa/vespalib/stllike/string.h> + +#include "clock.h" +#include "counter.h" +#include "dimension.h" +#include "dummy_metrics_manager.h" +#include "gauge.h" +#include "json_formatter.h" +#include "label.h" +#include "metric_identifier.h" +#include "metric_name.h" +#include "metrics_manager.h" +#include "point_builder.h" +#include "point.h" +#include "producer.h" +#include "simple_metrics_manager.h" +#include "snapshots.h" diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp new file mode 100644 index 00000000000..049be4fd328 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.cpp @@ -0,0 +1,224 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "simple_metrics_manager.h" +#include "simple_tick.h" + +#include <vespa/log/log.h> +LOG_SETUP(".vespalib.metrics.simple_metrics_manager"); + +namespace vespalib { +namespace metrics { + +using Guard = std::lock_guard<std::mutex>; + +SimpleMetricsManager::SimpleMetricsManager(const SimpleManagerConfig &config, + Tick::UP tick_supplier) + : _metricNames(), + _dimensionNames(), + _labelValues(), + _pointMaps(), + _currentSamples(), + _tickSupplier(std::move(tick_supplier)), + _startTime(_tickSupplier->first()), + _curTime(_startTime), + _collectCnt(0), + _buckets(), + _firstBucket(0), + _maxBuckets(config.sliding_window_seconds), + _totalsBucket(0, _startTime, _startTime), + _thread(&SimpleMetricsManager::tickerLoop, this) +{ + if (_maxBuckets < 1) _maxBuckets = 1; + Point empty = pointFrom(PointMap::BackingMap()); + assert(empty.id() == 0); +} + +SimpleMetricsManager::~SimpleMetricsManager() +{ + stopThread(); +} + +std::shared_ptr<MetricsManager> +SimpleMetricsManager::create(const SimpleManagerConfig &config) +{ + return std::shared_ptr<MetricsManager>( + new SimpleMetricsManager(config, std::make_unique<SimpleTick>())); +} + +std::shared_ptr<MetricsManager> +SimpleMetricsManager::createForTest(const SimpleManagerConfig &config, + Tick::UP tick_supplier) +{ + return std::shared_ptr<MetricsManager>( + new SimpleMetricsManager(config, std::move(tick_supplier))); +} + +Counter +SimpleMetricsManager::counter(const vespalib::string &name, const vespalib::string &) +{ + size_t id = _metricNames.resolve(name); + _metricTypes.check(id, name, MetricTypes::MetricType::COUNTER); + LOG(debug, "counter with metric name %s -> %zu", name.c_str(), id); + return Counter(shared_from_this(), MetricName(id)); +} + +Gauge +SimpleMetricsManager::gauge(const vespalib::string &name, const vespalib::string &) +{ + size_t id = _metricNames.resolve(name); + _metricTypes.check(id, name, MetricTypes::MetricType::GAUGE); + LOG(debug, "gauge with metric name %s -> %zu", name.c_str(), id); + return Gauge(shared_from_this(), MetricName(id)); +} + +Bucket +SimpleMetricsManager::mergeBuckets() +{ + Guard bucketsGuard(_bucketsLock); + if (_buckets.size() > 0) { + TimeStamp startTime = _buckets[_firstBucket].startTime; + Bucket merger(0, startTime, startTime); + for (size_t i = 0; i < _buckets.size(); i++) { + size_t off = (_firstBucket + i) % _buckets.size(); + merger.merge(_buckets[off]); + } + merger.padMetrics(_totalsBucket); + return merger; + } + // no data + return Bucket(0, _startTime, _curTime); +} + +Bucket +SimpleMetricsManager::totalsBucket() +{ + Guard bucketsGuard(_bucketsLock); + return _totalsBucket; +} + +Snapshot +SimpleMetricsManager::snapshotFrom(const Bucket &bucket) +{ + std::vector<PointSnapshot> points; + + double s = bucket.startTime.count(); + double e = bucket.endTime.count(); + Snapshot snap(s, e); + { + for (size_t i = 0; i < _pointMaps.size(); ++i) { + const PointMap::BackingMap &map = _pointMaps.lookup(i).backingMap(); + PointSnapshot point; + for (const PointMap::BackingMap::value_type &kv : map) { + point.dimensions.emplace_back(nameFor(kv.first), valueFor(kv.second)); + } + snap.add(point); + } + } + for (const CounterAggregator& entry : bucket.counters) { + size_t ni = entry.idx.name().id(); + size_t pi = entry.idx.point().id(); + const vespalib::string &name = _metricNames.lookup(ni); + CounterSnapshot val(name, snap.points()[pi], entry); + snap.add(val); + } + for (const GaugeAggregator& entry : bucket.gauges) { + size_t ni = entry.idx.name().id(); + size_t pi = entry.idx.point().id(); + const vespalib::string &name = _metricNames.lookup(ni); + GaugeSnapshot val(name, snap.points()[pi], entry); + snap.add(val); + } + return snap; +} + +Snapshot +SimpleMetricsManager::snapshot() +{ + Bucket merged = mergeBuckets(); + return snapshotFrom(merged); +} + +Snapshot +SimpleMetricsManager::totalSnapshot() +{ + Bucket totals = totalsBucket(); + return snapshotFrom(totals); +} + +void +SimpleMetricsManager::collectCurrentSamples(TimeStamp prev, + TimeStamp curr) +{ + CurrentSamples samples; + _currentSamples.extract(samples); + Bucket newBucket(++_collectCnt, prev, curr); + newBucket.merge(samples); + + Guard guard(_bucketsLock); + _totalsBucket.merge(newBucket); + if (_buckets.size() < _maxBuckets) { + _buckets.push_back(std::move(newBucket)); + } else { + _buckets[_firstBucket] = std::move(newBucket); + _firstBucket = (_firstBucket + 1) % _buckets.size(); + } +} + +Dimension +SimpleMetricsManager::dimension(const vespalib::string &name) +{ + size_t id = _dimensionNames.resolve(name); + LOG(debug, "dimension name %s -> %zu", name.c_str(), id); + return Dimension(id); +} + +Label +SimpleMetricsManager::label(const vespalib::string &value) +{ + size_t id = _labelValues.resolve(value); + LOG(debug, "label value %s -> %zu", value.c_str(), id); + return Label(id); +} + +PointBuilder +SimpleMetricsManager::pointBuilder(Point from) +{ + const PointMap &map = _pointMaps.lookup(from.id()); + return PointBuilder(shared_from_this(), map.backingMap()); +} + +Point +SimpleMetricsManager::pointFrom(PointMap::BackingMap map) +{ + size_t id = _pointMaps.resolve(PointMap(std::move(map))); + return Point(id); +} + + +void +SimpleMetricsManager::tickerLoop() +{ + while (_tickSupplier->alive()) { + TimeStamp now = _tickSupplier->next(_curTime); + if (_tickSupplier->alive()) { + tick(now); + } + } +} + +void +SimpleMetricsManager::stopThread() +{ + _tickSupplier->kill(); + _thread.join(); +} + +void +SimpleMetricsManager::tick(TimeStamp now) +{ + TimeStamp prev = _curTime; + collectCurrentSamples(prev, now); + _curTime = now; +} + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h new file mode 100644 index 00000000000..01725bb1739 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_metrics_manager.h @@ -0,0 +1,97 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <atomic> +#include <mutex> +#include <memory> +#include <thread> +#include <vespa/vespalib/stllike/string.h> +#include "name_collection.h" +#include "current_samples.h" +#include "snapshots.h" +#include "metrics_manager.h" +#include "metric_types.h" +#include "clock.h" +#include "point_map_collection.h" +#include "bucket.h" + +namespace vespalib { +namespace metrics { + +struct SimpleManagerConfig { + int sliding_window_seconds; + // possibly more config later + SimpleManagerConfig() : sliding_window_seconds(60) {} +}; + + +/** + * Simple manager class that puts everything into a + * single global repo with std::mutex locks used around + * most operations. Only implements sliding window + * and a fixed (1 Hz) collecting interval. + * XXX: Consider renaming this to "SlidingWindowManager". + **/ +class SimpleMetricsManager : public MetricsManager +{ +private: + NameCollection _metricNames; + MetricTypes _metricTypes; + NameCollection _dimensionNames; + NameCollection _labelValues; + PointMapCollection _pointMaps; + + const vespalib::string& nameFor(Dimension dimension) { return _dimensionNames.lookup(dimension.id()); } + const vespalib::string& valueFor(Label label) { return _labelValues.lookup(label.id()); } + + CurrentSamples _currentSamples; + + Tick::UP _tickSupplier; + TimeStamp _startTime; + TimeStamp _curTime; + + std::mutex _bucketsLock; + size_t _collectCnt; + std::vector<Bucket> _buckets; + size_t _firstBucket; + size_t _maxBuckets; + Bucket _totalsBucket; + + std::thread _thread; + void tickerLoop(); + void stopThread(); + void tick(TimeStamp now); // called once per second from another thread + + void collectCurrentSamples(TimeStamp prev, TimeStamp curr); + Bucket mergeBuckets(); + Bucket totalsBucket(); + Snapshot snapshotFrom(const Bucket &bucket); + + SimpleMetricsManager(const SimpleManagerConfig &config, + Tick::UP tick_supplier); +public: + ~SimpleMetricsManager(); + static std::shared_ptr<MetricsManager> create(const SimpleManagerConfig &config); + static std::shared_ptr<MetricsManager> createForTest(const SimpleManagerConfig &config, + Tick::UP tick_supplier); + Counter counter(const vespalib::string &name, const vespalib::string &description) override; + Gauge gauge(const vespalib::string &name, const vespalib::string &description) override; + Dimension dimension(const vespalib::string &name) override; + Label label(const vespalib::string &value) override; + PointBuilder pointBuilder(Point from) override; + Point pointFrom(PointMap::BackingMap map) override; + Snapshot snapshot() override; + Snapshot totalSnapshot() override; + + // for use from Counter only + void add(Counter::Increment inc) override { + _currentSamples.add(inc); + } + // for use from Gauge only + void sample(Gauge::Measurement value) override { + _currentSamples.sample(value); + } +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp new file mode 100644 index 00000000000..31a04e67ac4 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.cpp @@ -0,0 +1,62 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "simple_tick.h" + +namespace vespalib::metrics { + +namespace { + +const TimeStamp oneSec{1.0}; + +TimeStamp now() +{ + using Clock = std::chrono::system_clock; + Clock::time_point now = Clock::now(); + return now.time_since_epoch(); +} + +} // namespace <unnamed> + +SimpleTick::SimpleTick() + : _lock(), _runFlag(true), _cond() +{} + +TimeStamp +SimpleTick::first() +{ + return now(); +} + +TimeStamp +SimpleTick::next(TimeStamp prev) +{ + std::unique_lock<std::mutex> locker(_lock); + while (_runFlag) { + TimeStamp curr = now(); + if (curr - prev >= oneSec) { + return curr; + } else if (curr < prev) { + // clock was adjusted backwards + prev = curr; + _cond.wait_for(locker, oneSec); + } else { + _cond.wait_for(locker, oneSec - (curr - prev)); + } + } + return now(); +} + +void +SimpleTick::kill() +{ + std::unique_lock<std::mutex> locker(_lock); + _runFlag.store(false); + _cond.notify_all(); +} + +bool +SimpleTick::alive() const +{ + return _runFlag; +} + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h new file mode 100644 index 00000000000..74b9d88d255 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/simple_tick.h @@ -0,0 +1,26 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "clock.h" + +#include <atomic> +#include <condition_variable> +#include <mutex> + +namespace vespalib::metrics { + +// internal +class SimpleTick : public Tick { +private: + std::mutex _lock; + std::atomic<bool> _runFlag; + std::condition_variable _cond; +public: + SimpleTick(); + TimeStamp first() override; + TimeStamp next(TimeStamp prev) override; + void kill() override; + bool alive() const override; +}; + +} // namespace vespalib::metrics diff --git a/staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp new file mode 100644 index 00000000000..46de7beaac3 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.cpp @@ -0,0 +1,8 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "snapshots.h" + +namespace vespalib { +namespace metrics { + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/snapshots.h b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.h new file mode 100644 index 00000000000..0e9915cfb6d --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/snapshots.h @@ -0,0 +1,106 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/stllike/string.h> +#include <vector> +#include "counter_aggregator.h" +#include "gauge_aggregator.h" + +namespace vespalib { +namespace metrics { + +class DimensionBinding { +private: + const vespalib::string _dimensionName; + const vespalib::string _labelValue; +public: + const vespalib::string &dimensionName() const { return _dimensionName; } + const vespalib::string &labelValue() const { return _labelValue; } + DimensionBinding(const vespalib::string &a, + const vespalib::string &v) + : _dimensionName(a), _labelValue(v) + {} + ~DimensionBinding() {} +}; + +struct PointSnapshot { + std::vector<DimensionBinding> dimensions; +}; + +class CounterSnapshot { +private: + const vespalib::string _name; + const PointSnapshot &_point; + const size_t _count; +public: + CounterSnapshot(const vespalib::string &n, const PointSnapshot &p, const CounterAggregator &c) + : _name(n), _point(p), _count(c.count) + {} + ~CounterSnapshot() {} + const vespalib::string &name() const { return _name; } + const PointSnapshot &point() const { return _point; } + size_t count() const { return _count; } +}; + +class GaugeSnapshot { +private: + const vespalib::string _name; + const PointSnapshot &_point; + const size_t _observedCount; + const double _averageValue; + const double _minValue; + const double _maxValue; + const double _lastValue; +public: + GaugeSnapshot(const vespalib::string &n, const PointSnapshot &p, const GaugeAggregator &c) + : _name(n), + _point(p), + _observedCount(c.observedCount), + _averageValue(c.sumValue / (c.observedCount > 0 ? c.observedCount : 1)), + _minValue(c.minValue), + _maxValue(c.maxValue), + _lastValue(c.lastValue) + {} + ~GaugeSnapshot() {} + const vespalib::string &name() const { return _name; } + const PointSnapshot &point() const { return _point; } + size_t observedCount() const { return _observedCount; } + double averageValue() const { return _averageValue; } + double minValue() const { return _minValue; } + double maxValue() const { return _maxValue; } + double lastValue() const { return _lastValue; } +}; + +class Snapshot { +private: + double _start; + double _end; + std::vector<CounterSnapshot> _counters; + std::vector<GaugeSnapshot> _gauges; + std::vector<PointSnapshot> _points; +public: + double startTime() const { return _start; }; // seconds since 1970 + double endTime() const { return _end; }; // seconds since 1970 + + const std::vector<CounterSnapshot> &counters() const { + return _counters; + } + const std::vector<GaugeSnapshot> &gauges() const { + return _gauges; + } + const std::vector<PointSnapshot> &points() const { + return _points; + } + + // builders: + Snapshot(double s, double e) + : _start(s), _end(e), _counters(), _gauges() + {} + ~Snapshot() {} + void add(const PointSnapshot &entry) { _points.push_back(entry); } + void add(const CounterSnapshot &entry) { _counters.push_back(entry); } + void add(const GaugeSnapshot &entry) { _gauges.push_back(entry); } +}; + +} // namespace vespalib::metrics +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp new file mode 100644 index 00000000000..dd649133988 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.cpp @@ -0,0 +1,4 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "stable_store.h" + diff --git a/staging_vespalib/src/vespa/vespalib/metrics/stable_store.h b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.h new file mode 100644 index 00000000000..b62e0f2a94e --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/metrics/stable_store.h @@ -0,0 +1,88 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <memory> +#include <vector> +#include <assert.h> + +namespace vespalib { + +/** metrics-internal utility class */ +template <typename T> +class StableStore +{ + using MyClass = StableStore<T>; + template<typename U> + friend void swap(StableStore<U> &a, StableStore<U> &b); +public: + typedef std::unique_ptr<MyClass> UP; + + StableStore(); + ~StableStore() {} + + void add(T t) { + size_t sz = _mine.size(); + if (sz == _mine.capacity()) { + UP next(new MyClass(_size, std::move(_more), std::move(_mine)));; + _mine.clear(); + _mine.reserve(sz << 1); + _more = std::move(next); + } + _mine.push_back(t); + ++_size; + } + + template<typename FUNC> + void for_each(FUNC &&func) const { + std::vector<const MyClass *> vv; + dffill(vv); + for (const MyClass *p : vv) { + for (const T& elem : p->_mine) { + func(elem); + } + } + } + + size_t size() const { return _size; } + +private: + void dffill(std::vector<const MyClass *> &vv) const { + if (_more) { _more->dffill(vv); } + vv.push_back(this); + } + + StableStore(size_t sz, UP &&more, std::vector<T> &&mine); + + size_t _size; + UP _more; + std::vector<T> _mine; +}; + +template<typename T> +StableStore<T>::StableStore() + : _size(0), + _more(), + _mine() +{ + _mine.reserve(3); +} + +template<typename T> +StableStore<T>::StableStore(size_t sz, UP &&more, std::vector<T> &&mine) + : _size(sz), + _more(std::move(more)), + _mine(std::move(mine)) +{} + +template <typename T> +void swap(StableStore<T> &a, + StableStore<T> &b) +{ + using std::swap; + swap(a._size, b._size); + swap(a._mine, b._mine); + swap(a._more, b._more); +} + +} // namespace vespalib diff --git a/standalone-container/pom.xml b/standalone-container/pom.xml index 9300181e00b..0d66951f364 100644 --- a/standalone-container/pom.xml +++ b/standalone-container/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>standalone-container</artifactId> <version>6-SNAPSHOT</version> diff --git a/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala b/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala index fe1af676b54..41e6b66b986 100644 --- a/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala +++ b/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala @@ -10,8 +10,8 @@ import scala.language.implicitConversions import scala.util.Try /** - * @author tonytv - */ + * @author Tony Vaagenes + */ class CloudConfigYinstVariables extends CloudConfigOptions { import CloudConfigYinstVariables._ @@ -39,6 +39,7 @@ class CloudConfigYinstVariables extends CloudConfigOptions { override val dockerRegistry = optionalYinstVar[java.lang.String]("docker_registry") override val dockerVespaBaseImage = optionalYinstVar[java.lang.String]("docker_vespa_base_image") override val loadBalancerAddress = optionalYinstVar[java.lang.String]("load_balancer_address") + override val disableFiledistributor = optionalYinstVar[java.lang.Boolean]("disable_filedistributor") } object CloudConfigYinstVariables { diff --git a/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala b/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala index 1d4b83ce7d3..e1d5ba6577d 100644 --- a/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala +++ b/standalone-container/src/main/scala/com/yahoo/container/standalone/StandaloneContainerApplication.scala @@ -158,7 +158,7 @@ object StandaloneContainerApplication { val logger = new BaseDeployLogger val rawApplicationPackage = new FilesApplicationPackage.Builder(applicationPath.toFile).includeSourceFiles(true).preprocessedDir(preprocessedApplicationDir).build() // TODO: Needed until we get rid of semantic rules - val applicationPackage = rawApplicationPackage.preprocess(Zone.defaultZone(), new RuleConfigDeriver { + val applicationPackage = rawApplicationPackage.preprocess(Zone.defaultZone().id, new RuleConfigDeriver { override def derive(ruleBaseDir: String, outputDir: String): Unit = {} }, logger) validateApplication(applicationPackage) diff --git a/standalone-container/src/main/sh/standalone-container.sh b/standalone-container/src/main/sh/standalone-container.sh new file mode 100755 index 00000000000..dd0693f6f85 --- /dev/null +++ b/standalone-container/src/main/sh/standalone-container.sh @@ -0,0 +1,309 @@ +#!/bin/bash +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# BEGIN environment bootstrap section +# Do not edit between here and END as this section should stay identical in all scripts + +findpath () { + myname=${0} + mypath=${myname%/*} + myname=${myname##*/} + if [ "$mypath" ] && [ -d "$mypath" ]; then + return + fi + mypath=$(pwd) + if [ -f "${mypath}/${myname}" ]; then + return + fi + echo "FATAL: Could not figure out the path where $myname lives from $0" + exit 1 +} + +COMMON_ENV=libexec/vespa/common-env.sh + +source_common_env () { + if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then + export VESPA_HOME + common_env=$VESPA_HOME/$COMMON_ENV + if [ -f "$common_env" ]; then + . $common_env + return + fi + fi + return 1 +} + +findroot () { + source_common_env && return + if [ "$VESPA_HOME" ]; then + echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" + exit 1 + fi + if [ "$ROOT" ] && [ -d "$ROOT" ]; then + VESPA_HOME="$ROOT" + source_common_env && return + fi + findpath + while [ "$mypath" ]; do + VESPA_HOME=${mypath} + source_common_env && return + mypath=${mypath%/*} + done + echo "FATAL: missing VESPA_HOME environment variable" + echo "Could not locate $COMMON_ENV anywhere" + exit 1 +} + +findroot + +# END environment bootstrap section + +Usage() { + cat <<EOF +Usage: ${0##*/} start [OPTION]... +Usage: ${0##*/} stop [OPTION]... +Manage Vespa standalone jdisc container service. + +Options: + -u USER Run as USER. Overrides any VESPA_USER environment variable. + -s SERVICE The service name. +EOF + + exit 1 +} + +Fail() { + printf "%s\n" "$*" + exit 1 +} + +FixDataDirectory() { + if ! [ -d "$1" ]; then + echo "Creating data directory '$1'" + mkdir -p "$1" || exit 1 + fi + chown "${VESPA_USER}" "$1" + chmod 755 "$1" +} + +StartCommand() { + local service="$1" + shift + + if (( $# > 0 )); then + Fail "Too many arguments" + fi + + local service_regex='^[0-9a-zA-Z_-]+$' + if ! [[ "$service" =~ $service_regex ]]; then + Fail "Service must match regex '$service_regex'" + fi + + # common setup + export VESPA_SERVICE_NAME="$service" + + # stuff for the process: + local appdir="$VESPA_HOME/conf/$service-app" + local pidfile="$VESPA_HOME/var/run/$service.pid" + local cfpfile="$VESPA_HOME/var/jdisc_core/$service.properties" + local bundlecachedir="$VESPA_HOME/var/vespa/bundlecache/$service" + + cd "$VESPA_HOME" || Fail "Cannot cd to $VESPA_HOME" + + fixlimits + checkjava + + local vespa_log="$VESPA_HOME/logs/vespa/vespa.log" + export VESPA_LOG_TARGET="file:$vespa_log" + FixDataDirectory "$(dirname "$vespa_log")" + + export VESPA_LOG_CONTROL_FILE="$VESPA_HOME/var/db/vespa/logcontrol/$service.logcontrol" + export VESPA_LOG_CONTROL_DIR="$(dirname "$VESPA_LOG_CONTROL_FILE")" + FixDataDirectory "$VESPA_LOG_CONTROL_DIR" + + # Does not need fast allocation + export MALLOC_ARENA_MAX=1 + + # will be picked up by standalone-container: + export standalone_jdisc_container__app_location="$appdir" + + # class path + CP="$VESPA_HOME/lib/jars/jdisc_core-jar-with-dependencies.jar" + + FixDataDirectory "$(dirname "$cfpfile")" + printenv > "$cfpfile" + FixDataDirectory "$bundlecachedir" + + java \ + -Xms128m -Xmx2048m \ + -XX:+PreserveFramePointer \ + -XX:+HeapDumpOnOutOfMemoryError \ + -XX:HeapDumpPath="$VESPA_HOME/var/crash" \ + -XX:OnOutOfMemoryError="kill -9 %p" \ + -Djava.library.path="$VESPA_HOME/lib64" \ + -Djava.awt.headless=true \ + -Dsun.rmi.dgc.client.gcInterval=3600000 \ + -Dsun.net.client.defaultConnectTimeout=5000 \ + -Dsun.net.client.defaultReadTimeout=60000 \ + -Djavax.net.ssl.keyStoreType=JKS \ + -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 \ + -Djdisc.logger.tag="jdisc/$service" \ + -Dfile.encoding=UTF-8 \ + -cp "$CP" \ + com.yahoo.jdisc.core.StandaloneMain standalone-container-jar-with-dependencies.jar & + + local pid="$!" + echo "$pid" > "$pidfile" +} + +Kill() { + local force="$1" + local expected_user="$2" + local expected_comm="$3" # Executable name only + local pid="$4" + + local -i now + if ! now=$(date +%s); then + Fail "Failed to get the current date in seconds since epoch" + fi + local -i timeout=$(( now + 300 )) + + local has_killed=false + + while true; do + local ps_output="" + if ! ps_output=$(ps -p "$pid" -o user= -o comm=); then + # success + return + fi + + local user comm + read -r user comm <<< "$ps_output" + + if test "$user" != "$expected_user"; then + echo "Warning: Pid collision ($pid): Expected user $expected_user but found $user." + echo "Will assume original process has died." + return + fi + + if test "$comm" != "$expected_comm"; then + echo "Warning: Pid collision ($pid): Expected program $expected_comm but found $comm." + echo "Will assume original process has died." + return + fi + + if ! "$has_killed"; then + if $force; then + if ! kill -KILL "$pid"; then + Fail "Failed to kill $pid" + fi + else + if ! kill "$pid"; then + Fail "Failed to kill $pid" + fi + fi + + has_killed=true + fi + + sleep 1 + + now=$(date +%s) + if (( now >= timeout )); then + Fail "Process $pid still exists after $timeout seconds, giving up" + fi + done +} + +StopCommand() { + local user="$1" + shift + + local force=false + while (( $# > 0 )); do + case "$1" in + -f|--force) + force=true + shift + ;; + *) break ;; + esac + done + + if (( $# != 1 )); then + Fail "Stop command takes exactly one argument" + fi + + local service="$1" + + local pidfile="$VESPA_HOME/var/run/$service.pid" + if ! test -r "$pidfile"; then + echo "$service is not running" + return + fi + + local pid=$(< "$pidfile") + if ! [[ "$pid" =~ ^[0-9]+$ ]]; then + Fail "Pid file '$pidfile' does not contain a valid pid: $pid" + fi + + Kill "$force" "$user" java "$pid" + rm -f "$pidfile" +} + +Main() { + if (( $# == 0 )); then + Usage + fi + + local command="$1" + shift + + local service="standalone/container" + local user="$VESPA_USER" + + while (( $# > 0 )); do + case "$1" in + --help|-h) Usage ;; + --service|-s) + service="$2" + shift 2 + ;; + --user|-u) + user="$2" + shift 2 + ;; + *) break ;; + esac + done + + # Service name will be included in paths and possibly environment variable + # names, so be restrictive. + local service_regex='^[a-zA-Z0-9_-]+$' + if test -z "$service"; then + Fail "SERVICE not specified" + elif ! [[ "$service" =~ $service_regex ]]; then + Fail "Service must math the regex '$service_regex'" + fi + + if ! getent passwd "$user" &> /dev/null; then + Fail "Bad user ($user): not found in passwd" + elif test "$(id -un)" != "$user"; then + Fail "${0##*/} must be started by $user" + fi + + case "$command" in + help) Usage ;; + start) StartCommand "$service" "$@" ;; + stop) StopCommand "$user" "$service" "$@" ;; + *) Fail "Unknown command '$command'" ;; + esac +} + +Main "$@" diff --git a/standalone-container/vespa-standalone-container.spec b/standalone-container/vespa-standalone-container.spec new file mode 100644 index 00000000000..5ab7e9409a5 --- /dev/null +++ b/standalone-container/vespa-standalone-container.spec @@ -0,0 +1,95 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# Force special prefix for Vespa +%define _prefix /opt/vespa + +# Hack to speed up jar packing for now. This does not affect the rpm size. +%define __jar_repack %{nil} + +Name: vespa-standalone-container +Version: %version +BuildArch: noarch +Release: 1%{?dist} +Summary: Vespa standalone JDisc container +Group: Applications/Databases +License: Commercial +URL: http://vespa.ai + +Requires: bash +Requires: java-1.8.0-openjdk-headless + +Conflicts: vespa + +%description +The Vespa standalone JDisc container is a runtime environment for Java +applications. + +%install +declare jars_dir=%buildroot%_prefix/lib/jars +mkdir -p "$jars_dir" + +declare -a dirs=( + jdisc_jetty/target/dependency + vespa_jersey2/target/dependency +) +for dir in "${dirs[@]}"; do + cp "$dir"/* "$jars_dir" +done + +declare -a modules=( + component + config-bundle + config-model-api + config-model + config-provisioning + configdefinitions + container-disc + container-jersey2 + container-search-and-docproc + defaults + docprocs + jdisc_core + jdisc_http_service + simplemetrics + standalone-container + vespaclient-container-plugin + zkfacade +) +for module in "${modules[@]}"; do + cp "$module"/target/"$module"-jar-with-dependencies.jar "$jars_dir" +done + +# vespajlib must be installed _without_ dependencies. +cp vespajlib/target/vespajlib.jar "$jars_dir" + +declare -a libexec_files=( + vespabase/src/common-env.sh + standalone-container/src/main/sh/standalone-container.sh +) +declare libexec_dir=%buildroot%_prefix/libexec/vespa +mkdir -p "$libexec_dir" +for file in "${libexec_files[@]}"; do + cp "$file" "$libexec_dir" +done + +%clean +rm -rf %buildroot + +%pre +getent group vespa >/dev/null || groupadd -r vespa +getent passwd vespa >/dev/null || \ + useradd -r -g vespa -d %_prefix -s /sbin/nologin \ + -c "Create owner of all Vespa data files" vespa +echo "pathmunge %_prefix/bin" > /etc/profile.d/vespa.sh +echo "export VESPA_HOME=%_prefix" >> /etc/profile.d/vespa.sh +chmod +x /etc/profile.d/vespa.sh + +%postun +if [ $1 -eq 0 ]; then # this is an uninstallation + rm -f /etc/profile.d/vespa.sh + userdel vespa +fi + +%files +%defattr(-,vespa,vespa,-) +%_prefix/* diff --git a/statistics/pom.xml b/statistics/pom.xml index 7bbef0ea3c0..521efa9a2a1 100644 --- a/statistics/pom.xml +++ b/statistics/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>statistics</artifactId> <version>6-SNAPSHOT</version> diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt index 24d5a2d27f2..269785d629e 100644 --- a/storage/CMakeLists.txt +++ b/storage/CMakeLists.txt @@ -35,7 +35,6 @@ vespa_define_module( src/vespa/storage/distributor/operations/external src/vespa/storage/distributor/operations/idealstate src/vespa/storage/frameworkimpl/component - src/vespa/storage/frameworkimpl/memory src/vespa/storage/frameworkimpl/status src/vespa/storage/frameworkimpl/thread src/vespa/storage/persistence @@ -60,12 +59,10 @@ vespa_define_module( src/tests/common src/tests/common/hostreporter src/tests/distributor - src/tests/frameworkimpl/memory src/tests/frameworkimpl/status src/tests/persistence src/tests/persistence/common src/tests/persistence/filestorage src/tests/storageserver - src/tests/storageutil src/tests/visiting ) diff --git a/storage/pom.xml b/storage/pom.xml index 49b103cbe34..e0b21d5a8a9 100644 --- a/storage/pom.xml +++ b/storage/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>storage</artifactId> <version>6-SNAPSHOT</version> diff --git a/storage/src/tests/CMakeLists.txt b/storage/src/tests/CMakeLists.txt index 7747e8bacc1..a0264bf2946 100644 --- a/storage/src/tests/CMakeLists.txt +++ b/storage/src/tests/CMakeLists.txt @@ -5,7 +5,6 @@ vespa_add_executable(storage_testrunner_app TEST DEPENDS storage_teststorageserver storage_testbucketmover - storage_teststorageutil storage_testvisiting storage_testbucketdb storage_testcommon @@ -13,7 +12,6 @@ vespa_add_executable(storage_testrunner_app TEST storage_testdistributor storage_testpersistence storage_testfilestorage - storage_testmemory storage_teststatus ) diff --git a/storage/src/tests/common/CMakeLists.txt b/storage/src/tests/common/CMakeLists.txt index 8dd4e969d04..991726c935b 100644 --- a/storage/src/tests/common/CMakeLists.txt +++ b/storage/src/tests/common/CMakeLists.txt @@ -2,6 +2,7 @@ vespa_add_library(storage_testcommon TEST SOURCES dummystoragelink.cpp + global_bucket_space_distribution_converter_test.cpp metricstest.cpp storagelinktest.cpp testhelper.cpp diff --git a/storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp b/storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp new file mode 100644 index 00000000000..d34bed304a0 --- /dev/null +++ b/storage/src/tests/common/global_bucket_space_distribution_converter_test.cpp @@ -0,0 +1,385 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/storage/common/global_bucket_space_distribution_converter.h> +#include <vespa/vdstestlib/cppunit/macros.h> +#include <vespa/config/config.h> +#include <vespa/config/print/asciiconfigwriter.h> +#include <vespa/config/print/asciiconfigreader.h> +#include <vespa/vdslib/state/clusterstate.h> +#include <random> + +namespace storage { + +struct GlobalBucketSpaceDistributionConverterTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(GlobalBucketSpaceDistributionConverterTest); + CPPUNIT_TEST(can_transform_flat_cluster_config); + CPPUNIT_TEST(can_transform_single_level_multi_group_config); + CPPUNIT_TEST(can_transform_multi_level_multi_group_config); + CPPUNIT_TEST(can_transform_heterogenous_multi_group_config); + CPPUNIT_TEST(config_retired_state_is_propagated); + CPPUNIT_TEST(group_capacities_are_propagated); + CPPUNIT_TEST(global_distribution_has_same_owner_distributors_as_default); + CPPUNIT_TEST_SUITE_END(); + + void can_transform_flat_cluster_config(); + void can_transform_single_level_multi_group_config(); + void can_transform_multi_level_multi_group_config(); + void can_transform_heterogenous_multi_group_config(); + void config_retired_state_is_propagated(); + void group_capacities_are_propagated(); + void global_distribution_has_same_owner_distributors_as_default(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(GlobalBucketSpaceDistributionConverterTest); + +using DistributionConfig = vespa::config::content::StorDistributionConfig; + +namespace { + +std::unique_ptr<DistributionConfig> string_to_config(const vespalib::string& cfg) { + vespalib::asciistream iss(cfg); + config::AsciiConfigReader<vespa::config::content::StorDistributionConfig> reader(iss); + return reader.read(); +} + +vespalib::string config_to_string(const DistributionConfig& cfg) { + vespalib::asciistream ost; + config::AsciiConfigWriter writer(ost); + writer.write(cfg); + return ost.str(); +} + +vespalib::string default_to_global_config(const vespalib::string& default_config) { + auto default_cfg = string_to_config(default_config); + auto as_global = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg); + return config_to_string(*as_global); +} + +} + +void GlobalBucketSpaceDistributionConverterTest::can_transform_flat_cluster_config() { + vespalib::string default_config( +R"(redundancy 1 +group[1] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions 1|* +group[0].nodes[3] +group[0].nodes[0].index 0 +group[0].nodes[1].index 1 +group[0].nodes[2].index 2 +)"); + + vespalib::string expected_global_config( +R"(redundancy 3 +initial_redundancy 0 +ensure_primary_persisted true +ready_copies 3 +active_per_leaf_group true +distributor_auto_ownership_transfer_on_whole_group_down true +group[0].index "invalid" +group[0].name "invalid" +group[0].capacity 1 +group[0].partitions "*" +group[0].nodes[0].index 0 +group[0].nodes[0].retired false +group[0].nodes[1].index 1 +group[0].nodes[1].retired false +group[0].nodes[2].index 2 +group[0].nodes[2].retired false +disk_distribution MODULO_BID +)"); + CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config)); +} + + +void GlobalBucketSpaceDistributionConverterTest::can_transform_single_level_multi_group_config() { + vespalib::string default_config( +R"(redundancy 2 +group[3] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions 1|* +group[0].nodes[0] +group[1].name rack0 +group[1].index 0 +group[1].nodes[3] +group[1].nodes[0].index 0 +group[1].nodes[1].index 1 +group[1].nodes[2].index 2 +group[2].name rack1 +group[2].index 1 +group[2].nodes[3] +group[2].nodes[0].index 3 +group[2].nodes[1].index 4 +group[2].nodes[2].index 5 +)"); + + // The config converter cannot distinguish between default values + // and explicitly set ones, so we get a few more entries in our output + // config string. + // Most crucial parts of the transformed config is the root redundancy + // and the new partition config. We test _all_ config fields here so that + // we catch anything we miss transferring state of. + vespalib::string expected_global_config( +R"(redundancy 6 +initial_redundancy 0 +ensure_primary_persisted true +ready_copies 6 +active_per_leaf_group true +distributor_auto_ownership_transfer_on_whole_group_down true +group[0].index "invalid" +group[0].name "invalid" +group[0].capacity 1 +group[0].partitions "3|3|*" +group[1].index "0" +group[1].name "rack0" +group[1].capacity 1 +group[1].partitions "" +group[1].nodes[0].index 0 +group[1].nodes[0].retired false +group[1].nodes[1].index 1 +group[1].nodes[1].retired false +group[1].nodes[2].index 2 +group[1].nodes[2].retired false +group[2].index "1" +group[2].name "rack1" +group[2].capacity 1 +group[2].partitions "" +group[2].nodes[0].index 3 +group[2].nodes[0].retired false +group[2].nodes[1].index 4 +group[2].nodes[1].retired false +group[2].nodes[2].index 5 +group[2].nodes[2].retired false +disk_distribution MODULO_BID +)"); + CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config)); +} + +void GlobalBucketSpaceDistributionConverterTest::can_transform_multi_level_multi_group_config() { + vespalib::string default_config( +R"(redundancy 2 +group[5] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions *|* +group[0].nodes[0] +group[1].name switch0 +group[1].index 0 +group[1].partitions 1|* +group[1].nodes[0] +group[2].name rack0 +group[2].index 0.0 +group[2].nodes[1] +group[2].nodes[0].index 0 +group[3].name rack1 +group[3].index 0.1 +group[3].nodes[1] +group[3].nodes[0].index 1 +group[4].name switch0 +group[4].index 1 +group[4].partitions * +group[4].nodes[0] +group[5].name rack0 +group[5].index 1.0 +group[5].nodes[1] +group[5].nodes[0].index 2 +group[6].name rack1 +group[6].index 1.1 +group[6].nodes[1] +group[6].nodes[0].index 3 +)"); + + // Note: leaf groups do not have a partition spec, only inner groups. + vespalib::string expected_global_config( +R"(redundancy 4 +initial_redundancy 0 +ensure_primary_persisted true +ready_copies 4 +active_per_leaf_group true +distributor_auto_ownership_transfer_on_whole_group_down true +group[0].index "invalid" +group[0].name "invalid" +group[0].capacity 1 +group[0].partitions "2|2|*" +group[1].index "0" +group[1].name "switch0" +group[1].capacity 1 +group[1].partitions "1|1|*" +group[2].index "0.0" +group[2].name "rack0" +group[2].capacity 1 +group[2].partitions "" +group[2].nodes[0].index 0 +group[2].nodes[0].retired false +group[3].index "0.1" +group[3].name "rack1" +group[3].capacity 1 +group[3].partitions "" +group[3].nodes[0].index 1 +group[3].nodes[0].retired false +group[4].index "1" +group[4].name "switch0" +group[4].capacity 1 +group[4].partitions "1|1|*" +group[5].index "1.0" +group[5].name "rack0" +group[5].capacity 1 +group[5].partitions "" +group[5].nodes[0].index 2 +group[5].nodes[0].retired false +group[6].index "1.1" +group[6].name "rack1" +group[6].capacity 1 +group[6].partitions "" +group[6].nodes[0].index 3 +group[6].nodes[0].retired false +disk_distribution MODULO_BID +)"); + CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config)); +} + +void GlobalBucketSpaceDistributionConverterTest::can_transform_heterogenous_multi_group_config() { + vespalib::string default_config( +R"(redundancy 2 +ready_copies 2 +group[3] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions 1|* +group[0].nodes[0] +group[1].name rack0 +group[1].index 0 +group[1].nodes[1] +group[1].nodes[0].index 0 +group[2].name rack1 +group[2].index 1 +group[2].nodes[2] +group[2].nodes[0].index 1 +group[2].nodes[1].index 2 +)"); + + vespalib::string expected_global_config( +R"(redundancy 3 +initial_redundancy 0 +ensure_primary_persisted true +ready_copies 3 +active_per_leaf_group true +distributor_auto_ownership_transfer_on_whole_group_down true +group[0].index "invalid" +group[0].name "invalid" +group[0].capacity 1 +group[0].partitions "1|2|*" +group[1].index "0" +group[1].name "rack0" +group[1].capacity 1 +group[1].partitions "" +group[1].nodes[0].index 0 +group[1].nodes[0].retired false +group[2].index "1" +group[2].name "rack1" +group[2].capacity 1 +group[2].partitions "" +group[2].nodes[0].index 1 +group[2].nodes[0].retired false +group[2].nodes[1].index 2 +group[2].nodes[1].retired false +disk_distribution MODULO_BID +)"); + CPPUNIT_ASSERT_EQUAL(expected_global_config, default_to_global_config(default_config)); +} + +void GlobalBucketSpaceDistributionConverterTest::config_retired_state_is_propagated() { + vespalib::string default_config( +R"(redundancy 1 +group[1] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions 1|* +group[0].nodes[3] +group[0].nodes[0].index 0 +group[0].nodes[0].retired false +group[0].nodes[1].index 1 +group[0].nodes[1].retired true +group[0].nodes[2].index 2 +group[0].nodes[2].retired true +)"); + + auto default_cfg = string_to_config(default_config); + auto as_global = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg); + + CPPUNIT_ASSERT_EQUAL(size_t(1), as_global->group.size()); + CPPUNIT_ASSERT_EQUAL(size_t(3), as_global->group[0].nodes.size()); + CPPUNIT_ASSERT_EQUAL(false, as_global->group[0].nodes[0].retired); + CPPUNIT_ASSERT_EQUAL(true, as_global->group[0].nodes[1].retired); + CPPUNIT_ASSERT_EQUAL(true, as_global->group[0].nodes[2].retired); +} + +void GlobalBucketSpaceDistributionConverterTest::group_capacities_are_propagated() { + vespalib::string default_config( +R"(redundancy 2 +group[3] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions 1|* +group[0].capacity 5 +group[0].nodes[0] +group[1].name rack0 +group[1].index 0 +group[1].capacity 2 +group[1].nodes[1] +group[1].nodes[0].index 0 +group[2].name rack1 +group[2].capacity 3 +group[2].index 1 +group[2].nodes[1] +group[2].nodes[0].index 1 +)"); + auto default_cfg = string_to_config(default_config); + auto as_global = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg); + + CPPUNIT_ASSERT_EQUAL(size_t(3), as_global->group.size()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, as_global->group[0].capacity, 0.00001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, as_global->group[1].capacity, 0.00001); + CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, as_global->group[2].capacity, 0.00001); +} + +void GlobalBucketSpaceDistributionConverterTest::global_distribution_has_same_owner_distributors_as_default() { + vespalib::string default_config( +R"(redundancy 2 +ready_copies 2 +group[3] +group[0].name "invalid" +group[0].index "invalid" +group[0].partitions 1|* +group[0].nodes[0] +group[1].name rack0 +group[1].index 0 +group[1].nodes[1] +group[1].nodes[0].index 0 +group[2].name rack1 +group[2].index 1 +group[2].nodes[2] +group[2].nodes[0].index 1 +group[2].nodes[1].index 2 +)"); + + auto default_cfg = string_to_config(default_config); + auto global_cfg = GlobalBucketSpaceDistributionConverter::convert_to_global(*default_cfg); + + lib::Distribution default_distr(*default_cfg); + lib::Distribution global_distr(*global_cfg); + lib::ClusterState state("distributor:6 storage:6"); + + std::mt19937 rng; + std::uniform_int_distribution<uint64_t> d(0, UINT64_MAX); + for (int i = 0; i < 100; ++i) { + document::BucketId bucket(16, d(rng)); + const auto default_index = default_distr.getIdealDistributorNode(state, bucket, "ui"); + const auto global_index = global_distr.getIdealDistributorNode(state, bucket, "ui"); + CPPUNIT_ASSERT_EQUAL(default_index, global_index); + } +} + +}
\ No newline at end of file diff --git a/storage/src/tests/common/teststorageapp.h b/storage/src/tests/common/teststorageapp.h index 6906d41ac47..46744ecb3c6 100644 --- a/storage/src/tests/common/teststorageapp.h +++ b/storage/src/tests/common/teststorageapp.h @@ -24,7 +24,6 @@ #include <vespa/storage/storageserver/framework.h> #include <vespa/storage/frameworkimpl/component/distributorcomponentregisterimpl.h> #include <vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.h> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageframework/defaultimplementation/clock/realclock.h> #include <vespa/storageframework/defaultimplementation/component/testcomponentregister.h> #include <vespa/persistence/spi/persistenceprovider.h> diff --git a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp index a7418629f81..16d317551e0 100644 --- a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp +++ b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp @@ -1070,9 +1070,8 @@ TwoPhaseUpdateOperationTest::testSafePathConditionParseFailureFailsWithIllegalPa "timestamp 0, timestamp of updated doc: 0) " "ReturnCode(ILLEGAL_PARAMETERS, " "Failed to parse test and set condition: " - "Unexpected token at position 16 " - "('==fran...c') in query 'testdoctype1." - "san==fran...cisco',)"s, + "syntax error, unexpected . at column 24 when " + "parsing selection 'testdoctype1.san==fran...cisco')"s, sender.getLastReply(true)); } @@ -1096,7 +1095,8 @@ TwoPhaseUpdateOperationTest::testSafePathConditonUnknownDocTypeFailsWithIllegalP "timestamp 0, timestamp of updated doc: 0) " "ReturnCode(ILLEGAL_PARAMETERS, " "Failed to parse test and set condition: " - "Document type langbein not found)"s, + "Document type 'langbein' not found at column 1 " + "when parsing selection 'langbein.headerval=1234')"s, sender.getLastReply(true)); } diff --git a/storage/src/tests/distributor/visitoroperationtest.cpp b/storage/src/tests/distributor/visitoroperationtest.cpp index 26f4fb3e784..972ccf41bfe 100644 --- a/storage/src/tests/distributor/visitoroperationtest.cpp +++ b/storage/src/tests/distributor/visitoroperationtest.cpp @@ -454,7 +454,8 @@ VisitorOperationTest::testInvalidOrderDocSelection() CPPUNIT_ASSERT_EQUAL( std::string("CreateVisitorReply(last=BucketId(0x0000000000000000)) " "ReturnCode(ILLEGAL_PARAMETERS, Failed to parse document select " - "string 'id.order(10,3)=1 and dummy': Document type dummy not found)"), + "string 'id.order(10,3)=1 and dummy': Document type 'dummy' not " + "found at column 22 when parsing selection 'id.order(10,3)=1 and dummy')"), runEmptyVisitor( createVisitorCommand("invalidOrderDoc", id, diff --git a/storage/src/tests/frameworkimpl/memory/CMakeLists.txt b/storage/src/tests/frameworkimpl/memory/CMakeLists.txt deleted file mode 100644 index e5a364e02a3..00000000000 --- a/storage/src/tests/frameworkimpl/memory/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(storage_testmemory TEST - SOURCES - memorystatusviewertest.cpp - DEPENDS - storage - storage_testcommon -) diff --git a/storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp b/storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp deleted file mode 100644 index 1ad4b16751d..00000000000 --- a/storage/src/tests/frameworkimpl/memory/memorystatusviewertest.cpp +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/metrics/metrics.h> -#include <vespa/metrics/metricmanager.h> -#include <vespa/storage/frameworkimpl/memory/memorystatusviewer.h> -#include <vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h> -#include <tests/common/teststorageapp.h> -#include <vespa/vdstestlib/cppunit/macros.h> -#include <vespa/vespalib/util/exceptions.h> -#include <boost/lexical_cast.hpp> - -namespace storage { - -struct MemoryStatusViewerTest : public CppUnit::TestFixture -{ - static const int maxMemory = 1000; - std::unique_ptr<TestServiceLayerApp> _node; - std::unique_ptr<framework::defaultimplementation::MemoryManager> _memMan; - - void setUp() override; - - void testEmptyState(); - void testSnapshots(); - - CPPUNIT_TEST_SUITE(MemoryStatusViewerTest); - CPPUNIT_TEST(testEmptyState); - CPPUNIT_TEST(testSnapshots); - CPPUNIT_TEST_SUITE_END(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION(MemoryStatusViewerTest); - -void -MemoryStatusViewerTest::setUp() -{ - _node.reset(new TestServiceLayerApp(DiskCount(2))); - framework::defaultimplementation::PriorityMemoryLogic* logic( - new framework::defaultimplementation::PriorityMemoryLogic( - _node->getClock(), maxMemory)); - logic->setMinJumpToUpdateMax(1); - _memMan.reset(new framework::defaultimplementation::MemoryManager( - framework::defaultimplementation::AllocationLogic::UP(logic))); -} - -void -MemoryStatusViewerTest::testEmptyState() -{ - // Add a memory manager, and add a bit of load to it, so it's not - // totally empty. - StorageComponent component(_node->getComponentRegister(), "test"); - - metrics::MetricManager mm; - MemoryStatusViewer viewer( - *_memMan, mm, _node->getComponentRegister()); - std::ostringstream actual; - viewer.reportStatus(actual, framework::HttpUrlPath("/")); - CPPUNIT_ASSERT_MATCH_REGEX(".*Plotr.LineChart.*", actual.str()); - CPPUNIT_ASSERT_MATCH_REGEX( - ".*Current: 1970-01-01 00:00:00 Max memory 1000 SnapShot\\(Used 0, w/o cache 0\\).*", - actual.str()); - CPPUNIT_ASSERT_MATCH_REGEX( - ".*Last hour: na.*", actual.str()); -} - -namespace { - void waitForProcessedTime( - const MemoryStatusViewer& viewer, framework::SecondTime time, - framework::SecondTime timeout = framework::SecondTime(30)) - { - framework::defaultimplementation::RealClock clock; - framework::MilliSecTime endTime( - clock.getTimeInMillis() + timeout.getMillis()); - framework::SecondTime processedTime(0); - while (clock.getTimeInMillis() < endTime) { - processedTime = viewer.getProcessedTime(); - if (processedTime >= time) return; - FastOS_Thread::Sleep(1); - } - std::ostringstream ost; - ost << "Timed out waiting " << timeout << " ms for time " << time - << " to be processed. Currently time is only processed up to " - << processedTime; - throw new vespalib::IllegalStateException(ost.str(), VESPA_STRLOC); - } -} - -#define ASSERT_MEMORY(output, period, maxmem, used, usedwocache) \ -{ \ - std::string::size_type _pos1_(output.find(period)); \ - std::string::size_type _pos2_(output.find("Max memory", _pos1_)); \ - std::string::size_type _pos3_(output.find("SnapShot", _pos2_)); \ - std::string _maxMemory_(output.substr(_pos2_ + 11, _pos3_ - _pos2_ - 12)); \ - std::string::size_type _pos4_(output.find(",", _pos3_)); \ - std::string _used_(output.substr(_pos3_ + 14, _pos4_ - _pos3_ - 14)); \ - std::string::size_type _pos5_(output.find(")", _pos4_)); \ - std::string _usedwo_(output.substr(_pos4_ + 12, _pos5_ - _pos4_ - 12)); \ - std::ostringstream _failure_; \ - _failure_ << "Wrong match in period " << period << " in output:\n" \ - << output << "\nFor value: "; \ - \ - CPPUNIT_ASSERT_EQUAL_MSG(_failure_.str() + "Max memory", \ - uint64_t(maxmem), boost::lexical_cast<uint64_t>(_maxMemory_)); \ - CPPUNIT_ASSERT_EQUAL_MSG(_failure_.str() + "Used memory", \ - uint64_t(used), boost::lexical_cast<uint64_t>(_used_)); \ - CPPUNIT_ASSERT_EQUAL_MSG(_failure_.str() + "Used memory w/o cache", \ - uint64_t(usedwocache), boost::lexical_cast<uint64_t>(_usedwo_)); \ -} - -void -MemoryStatusViewerTest::testSnapshots() -{ - // Add a memory manager, and add a bit of load to it, so it's not - // totally empty. - StorageComponent component(_node->getComponentRegister(), "test"); - const framework::MemoryAllocationType putAlloc( - component.getMemoryManager().registerAllocationType( - framework::MemoryAllocationType("PUT"))); - const framework::MemoryAllocationType getAlloc( - component.getMemoryManager().registerAllocationType( - framework::MemoryAllocationType("GET"))); - - framework::MemoryToken::UP put = _memMan->allocate(putAlloc, 0, 100, 80); - framework::MemoryToken::UP get = _memMan->allocate(getAlloc, 30, 200, 50); - framework::MemoryToken::UP get2 = _memMan->allocate(getAlloc, 70, 150, 60); - - metrics::MetricManager mm; - MemoryStatusViewer viewer(*_memMan, mm, _node->getComponentRegister()); - - _node->getClock().addSecondsToTime(1000); - viewer.notifyThread(); - waitForProcessedTime(viewer, framework::SecondTime(1000)); - - std::ostringstream actual; - viewer.printDebugOutput(actual); - //std::cerr << actual.str() << "\n"; - ASSERT_MEMORY(actual.str(), "Current", 1000, 450, 450); - ASSERT_MEMORY(actual.str(), "Last hour", 1000, 450, 450); - ASSERT_MEMORY(actual.str(), "Last ever", 1000, 450, 450); - - put = _memMan->allocate(putAlloc, 0, 50, 80); - get = _memMan->allocate(getAlloc, 100, 140, 50); - get2 = _memMan->allocate(getAlloc, 20, 100, 70); - - _node->getClock().addSecondsToTime(3600); - viewer.notifyThread(); - waitForProcessedTime(viewer, framework::SecondTime(4600)); - - actual.str(""); - viewer.printDebugOutput(actual); - //std::cerr << actual.str() << "\n"; - ASSERT_MEMORY(actual.str(), "Current", 1000, 290, 290); - ASSERT_MEMORY(actual.str(), "Last hour", 1000, 540, 540); - ASSERT_MEMORY(actual.str(), "Last ever", 1000, 540, 540); - - get.reset(); - - _node->getClock().addSecondsToTime(3600); - viewer.notifyThread(); - waitForProcessedTime(viewer, framework::SecondTime(4600 + 3600)); - - actual.str(""); - viewer.printDebugOutput(actual); - //std::cerr << actual.str() << "\n"; - ASSERT_MEMORY(actual.str(), "Current", 1000, 150, 150); - ASSERT_MEMORY(actual.str(), "Last hour", 1000, 290, 290); - ASSERT_MEMORY(actual.str(), "Last ever", 1000, 540, 540); - -} - -} // storage diff --git a/storage/src/tests/persistence/filestorage/filestormanagertest.cpp b/storage/src/tests/persistence/filestorage/filestormanagertest.cpp index 67b93108139..af0082fa788 100644 --- a/storage/src/tests/persistence/filestorage/filestormanagertest.cpp +++ b/storage/src/tests/persistence/filestorage/filestormanagertest.cpp @@ -231,8 +231,6 @@ struct FileStorManagerTest : public CppUnit::TestFixture { fprintf(stderr, "%s\n", e.what()); } _testdoctype1 = _node->getTypeRepo()->getDocumentType("testdoctype1"); - _node->getMemoryManager().registerAllocationType( - framework::MemoryAllocationType("VISITOR_BUFFER")); } void putDoc(DummyStorageLink& top, @@ -2010,19 +2008,11 @@ FileStorManagerTest::testVisiting() top.reset(); // Visit bucket with no split, using no selection { - framework::MemoryToken::UP token( - _node->getMemoryManager().allocate( - _node->getMemoryManager().getAllocationType( - "VISITOR_BUFFER"), - 16*1024, - 16*1024, - 127)); spi::IteratorId iterId(createIterator(top, ids[0], "true")); - std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), makeDocumentBucket(ids[0]), iterId, 16*1024)); + auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(ids[0]), iterId, 16*1024); top.sendDown(cmd); top.waitForMessages(1, _waitTime); - CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies()); + CPPUNIT_ASSERT_EQUAL(size_t(1), top.getNumReplies()); std::shared_ptr<GetIterReply> reply( std::dynamic_pointer_cast<GetIterReply>(top.getReply(0))); CPPUNIT_ASSERT(reply.get()); @@ -2039,15 +2029,7 @@ FileStorManagerTest::testVisiting() ids[1], "testdoctype1.hstringval = \"John Doe\"")); while (true) { - framework::MemoryToken::UP token( - _node->getMemoryManager().allocate( - _node->getMemoryManager().getAllocationType( - "VISITOR_BUFFER"), - 16*1024, - 16*1024, - 127)); - std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), makeDocumentBucket(ids[1]), iterId, 16*1024)); + auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(ids[1]), iterId, 16*1024); top.sendDown(cmd); top.waitForMessages(1, _waitTime); CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies()); @@ -2078,18 +2060,10 @@ FileStorManagerTest::testVisiting() true)); uint32_t totalDocs = 0; while (true) { - framework::MemoryToken::UP token( - _node->getMemoryManager().allocate( - _node->getMemoryManager().getAllocationType( - "VISITOR_BUFFER"), - 16*1024, - 16*1024, - 127)); - std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), makeDocumentBucket(ids[1]), iterId, 16*1024)); + auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(ids[1]), iterId, 16*1024); top.sendDown(cmd); top.waitForMessages(1, _waitTime); - CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies()); + CPPUNIT_ASSERT_EQUAL(size_t(1), top.getNumReplies()); std::shared_ptr<GetIterReply> reply( std::dynamic_pointer_cast<GetIterReply>( top.getReply(0))); @@ -2799,15 +2773,8 @@ FileStorManagerTest::testGetIter() // Sending a getiter request that will only visit some of the docs spi::IteratorId iterId(createIterator(top, bid, "")); { - framework::MemoryToken::UP token( - _node->getMemoryManager().allocate( - _node->getMemoryManager().getAllocationType( - "VISITOR_BUFFER"), - 2048, - 2048, - 127)); std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), makeDocumentBucket(bid), iterId, 2048)); + new GetIterCommand(makeDocumentBucket(bid), iterId, 2048)); top.sendDown(cmd); top.waitForMessages(1, _waitTime); CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies()); @@ -2837,18 +2804,10 @@ FileStorManagerTest::testGetIter() CPPUNIT_ASSERT_EQUAL(ReturnCode(ReturnCode::OK), reply->getResult()); } { - framework::MemoryToken::UP token( - _node->getMemoryManager().allocate( - _node->getMemoryManager().getAllocationType( - "VISITOR_BUFFER"), - 2048, - 2048, - 127)); - std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), makeDocumentBucket(bid), iterId, 2048)); + auto cmd = std::make_shared<GetIterCommand>(makeDocumentBucket(bid), iterId, 2048); top.sendDown(cmd); top.waitForMessages(1, _waitTime); - CPPUNIT_ASSERT_EQUAL((size_t) 1, top.getNumReplies()); + CPPUNIT_ASSERT_EQUAL(size_t(1), top.getNumReplies()); std::shared_ptr<GetIterReply> reply( std::dynamic_pointer_cast<GetIterReply>( top.getReply(0))); diff --git a/storage/src/tests/persistence/persistencetestutils.cpp b/storage/src/tests/persistence/persistencetestutils.cpp index fcfbcd0a78b..9d3a3d5f008 100644 --- a/storage/src/tests/persistence/persistencetestutils.cpp +++ b/storage/src/tests/persistence/persistencetestutils.cpp @@ -11,7 +11,6 @@ #include <vespa/vespalib/util/exceptions.h> using document::DocumentType; -using storage::framework::defaultimplementation::AllocationLogic; using storage::spi::test::makeSpiBucket; using document::test::makeDocumentBucket; diff --git a/storage/src/tests/persistence/persistencetestutils.h b/storage/src/tests/persistence/persistencetestutils.h index ee87925a0bc..36b1fef7285 100644 --- a/storage/src/tests/persistence/persistencetestutils.h +++ b/storage/src/tests/persistence/persistencetestutils.h @@ -8,7 +8,6 @@ #include <vespa/storage/persistence/persistenceutil.h> #include <vespa/storage/common/messagesender.h> #include <vespa/storage/common/storagecomponent.h> -#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h> #include <vespa/persistence/spi/persistenceprovider.h> #include <vespa/persistence/dummyimpl/dummypersistence.h> #include <vespa/document/base/testdocman.h> diff --git a/storage/src/tests/storageserver/communicationmanagertest.cpp b/storage/src/tests/storageserver/communicationmanagertest.cpp index 5111368dd1c..cd5dd2a01a4 100644 --- a/storage/src/tests/storageserver/communicationmanagertest.cpp +++ b/storage/src/tests/storageserver/communicationmanagertest.cpp @@ -6,7 +6,6 @@ #include <vespa/messagebus/rpcmessagebus.h> #include <vespa/storageapi/message/persistence.h> #include <vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.h> -#include <vespa/storageframework/defaultimplementation/memory/nomemorymanager.h> #include <tests/common/teststorageapp.h> #include <tests/common/dummystoragelink.h> #include <tests/common/testhelper.h> diff --git a/storage/src/tests/storageutil/.gitignore b/storage/src/tests/storageutil/.gitignore deleted file mode 100644 index a080232d5f3..00000000000 --- a/storage/src/tests/storageutil/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -*.So -*.lo -*.o -.*.swp -.config.log -.depend -.depend.NEW -.deps -.libs -Makefile -statefile* -testrunner -testrunner.core diff --git a/storage/src/tests/storageutil/CMakeLists.txt b/storage/src/tests/storageutil/CMakeLists.txt deleted file mode 100644 index 68d2517bda3..00000000000 --- a/storage/src/tests/storageutil/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(storage_teststorageutil TEST - SOURCES - charttest.cpp - functortest.cpp - palettetest.cpp - DEPENDS - storage -) diff --git a/storage/src/tests/storageutil/charttest.cpp b/storage/src/tests/storageutil/charttest.cpp deleted file mode 100644 index d16f3f11747..00000000000 --- a/storage/src/tests/storageutil/charttest.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/storage/storageutil/piechart.h> -#include <vespa/vdstestlib/cppunit/macros.h> -#include <fstream> - -namespace storage { - -struct PieChartTest : public CppUnit::TestFixture -{ - void testWriteHtmlFile(); - - CPPUNIT_TEST_SUITE(PieChartTest); - CPPUNIT_TEST(testWriteHtmlFile); - CPPUNIT_TEST_SUITE_END(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION(PieChartTest); - -namespace { - void printHtmlFile(const std::string& filename, const PieChart& chart) { - std::ofstream out(filename.c_str()); - out << "<html>\n" - << " <head>\n" - << " "; - PieChart::printHtmlHeadAdditions(out, " "); - out << "\n <title>Pie example</title>\n" - << " </head>\n" - << " <body>\n" - << " "; - chart.printCanvas(out, 500, 400); - out << "\n "; - chart.printScript(out, " "); - out << "\n </body>\n" - << "</html>\n"; - out.close(); - } -} - -void -PieChartTest::testWriteHtmlFile() -{ - { - PieChart chart("mypie"); - chart.add(10, "put"); - chart.add(20, "get"); - chart.add(50, "free"); - - printHtmlFile("piefile.html", chart); - } - { - PieChart chart("mypie", PieChart::SCHEME_CUSTOM); - chart.add(10, "put", PieChart::RED); - chart.add(20, "get", PieChart::GREEN); - chart.add(50, "free", PieChart::BLUE); - - printHtmlFile("piefile-customcols.html", chart); - } -} - -} // storage diff --git a/storage/src/tests/storageutil/functortest.cpp b/storage/src/tests/storageutil/functortest.cpp deleted file mode 100644 index 0fa1eeaaa8a..00000000000 --- a/storage/src/tests/storageutil/functortest.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <cppunit/extensions/HelperMacros.h> -#include <list> -#include <string> -#include <algorithm> -#include <vespa/storage/storageutil/functor.h> - -class Functor_Test : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(Functor_Test); - CPPUNIT_TEST(testReplace); - CPPUNIT_TEST(testDeletePointer); - CPPUNIT_TEST_SUITE_END(); - -public: - -protected: - void testReplace(); - void testDeletePointer(); -}; - -using namespace storage; -using namespace std; - -CPPUNIT_TEST_SUITE_REGISTRATION(Functor_Test); - -void Functor_Test::testReplace() -{ - string source("this.is.a.string.with.many.dots."); - for_each(source.begin(), source.end(), Functor::Replace<char>('.', '_')); - CPPUNIT_ASSERT_EQUAL(string("this_is_a_string_with_many_dots_"), source); -} - -namespace { - - static int instanceCounter = 0; - - class TestClass { - public: - TestClass() { instanceCounter++; } - ~TestClass() { instanceCounter--; } - }; -} - -void Functor_Test::testDeletePointer() -{ - list<TestClass*> mylist; - mylist.push_back(new TestClass()); - mylist.push_back(new TestClass()); - mylist.push_back(new TestClass()); - CPPUNIT_ASSERT_EQUAL(3, instanceCounter); - for_each(mylist.begin(), mylist.end(), Functor::DeletePointer()); - CPPUNIT_ASSERT_EQUAL(0, instanceCounter); -} diff --git a/storage/src/tests/storageutil/palettetest.cpp b/storage/src/tests/storageutil/palettetest.cpp deleted file mode 100644 index a5d1b7f8b22..00000000000 --- a/storage/src/tests/storageutil/palettetest.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/storage/storageutil/palette.h> -#include <vespa/vdstestlib/cppunit/macros.h> - -namespace storage { - -struct PaletteTest : public CppUnit::TestFixture { - void testNormalUsage(); - - CPPUNIT_TEST_SUITE(PaletteTest); - CPPUNIT_TEST(testNormalUsage); - CPPUNIT_TEST_SUITE_END(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION(PaletteTest); - -void -PaletteTest::testNormalUsage() -{ - std::ofstream out("palette.html"); - out << "<html><body>\n"; - Palette palette(75); - palette.printHtmlTablePalette(out); - out << "</body></html>\n"; - out.close(); -} - -} // storage diff --git a/storage/src/vespa/storage/CMakeLists.txt b/storage/src/vespa/storage/CMakeLists.txt index 69846cafde2..d854c603097 100644 --- a/storage/src/vespa/storage/CMakeLists.txt +++ b/storage/src/vespa/storage/CMakeLists.txt @@ -12,7 +12,6 @@ vespa_add_library(storage $<TARGET_OBJECTS:storage_bucketmover> $<TARGET_OBJECTS:storage_thread> $<TARGET_OBJECTS:storage_status> - $<TARGET_OBJECTS:storage_memory> $<TARGET_OBJECTS:storage_component> INSTALL lib64 DEPENDS diff --git a/storage/src/vespa/storage/bucketdb/bucketmanager.cpp b/storage/src/vespa/storage/bucketdb/bucketmanager.cpp index 00fa5c95c9b..aa9158f9e5b 100644 --- a/storage/src/vespa/storage/bucketdb/bucketmanager.cpp +++ b/storage/src/vespa/storage/bucketdb/bucketmanager.cpp @@ -19,12 +19,13 @@ #include <vespa/vespalib/util/stringfmt.h> #include <vespa/vespalib/stllike/hash_map.hpp> #include <vespa/config/config.h> -#include <unordered_map> +#include <chrono> #include <vespa/log/bufferedlogger.h> LOG_SETUP(".storage.bucketdb.manager"); using document::BucketSpace; +using namespace std::chrono_literals; namespace storage { @@ -33,9 +34,8 @@ BucketManager::BucketManager(const config::ConfigUri & configUri, : StorageLinkQueued("Bucket manager", compReg), framework::StatusReporter("bucketdb", "Bucket database"), _configUri(configUri), - _stateAccess(), - _bucketDBMemoryToken(), - _workerMonitor(), + _workerLock(), + _workerCond(), _clusterStateLock(), _queueProcessingLock(), _queuedReplies(), @@ -47,12 +47,6 @@ BucketManager::BucketManager(const config::ConfigUri & configUri, _requestsCurrentlyProcessing(0), _component(compReg, "bucketmanager") { - const framework::MemoryAllocationType& allocType( - _component.getMemoryManager().registerAllocationType( - framework::MemoryAllocationType("DATABASE"))); - _bucketDBMemoryToken = _component.getMemoryManager().allocate( - allocType, 0, 0, api::StorageMessage::HIGH); - assert(_bucketDBMemoryToken.get() != 0); _metrics->setDisks(_component.getDiskCount()); _component.registerStatusPage(*this); _component.registerMetric(*_metrics); @@ -81,7 +75,7 @@ void BucketManager::onClose() { // Stop internal thread such that we don't send any more messages down. if (_thread.get() != 0) { - _thread->interruptAndJoin(&_workerMonitor); + _thread->interruptAndJoin(_workerLock, _workerCond); _thread.reset(0); } StorageLinkQueued::onClose(); @@ -221,8 +215,6 @@ BucketManager::updateMetrics(bool updateDocCount) LOG(debug, "Iterating bucket database to update metrics%s%s", updateDocCount ? "" : ", minusedbits only", _doneInitialized ? "" : ", server is not done initializing"); - uint64_t dbMemSize = _component.getBucketSpaceRepo().getBucketMemoryUsage(); - _bucketDBMemoryToken->resize(dbMemSize, dbMemSize); uint32_t diskCount = _component.getDiskCount(); if (!updateDocCount || _doneInitialized) { @@ -268,7 +260,7 @@ void BucketManager::run(framework::ThreadHandle& thread) bool didWork = false; BucketInfoRequestMap infoReqs; { - vespalib::MonitorGuard monitor(_workerMonitor); + std::lock_guard<std::mutex> guard(_workerLock); infoReqs.swap(_bucketInfoRequests); } @@ -277,12 +269,12 @@ void BucketManager::run(framework::ThreadHandle& thread) } { - vespalib::MonitorGuard monitor(_workerMonitor); + std::unique_lock<std::mutex> guard(_workerLock); for (const auto &req : infoReqs) { assert(req.second.empty()); } if (!didWork) { - monitor.wait(1000); + _workerCond.wait_for(guard, 1s); thread.registerTick(framework::WAIT_CYCLE); } else { thread.registerTick(framework::PROCESS_CYCLE); @@ -393,9 +385,9 @@ bool BucketManager::onRequestBucketInfo( LOG(debug, "Got request bucket info command"); if (cmd->getBuckets().size() == 0 && cmd->hasSystemState()) { - vespalib::MonitorGuard monitor(_workerMonitor); + std::lock_guard<std::mutex> guard(_workerLock); _bucketInfoRequests[cmd->getBucketSpace()].push_back(cmd); - monitor.signal(); + _workerCond.notify_all(); LOG(spam, "Scheduled request bucket info request for retrieval"); return true; } @@ -472,7 +464,7 @@ BucketManager::ScopedQueueDispatchGuard::~ScopedQueueDispatchGuard() void BucketManager::enterQueueProtectedSection() { - vespalib::LockGuard guard(_queueProcessingLock); + std::lock_guard<std::mutex> guard(_queueProcessingLock); ++_requestsCurrentlyProcessing; } @@ -480,7 +472,7 @@ void BucketManager::leaveQueueProtectedSection(ScopedQueueDispatchGuard& queueGuard) { (void) queueGuard; // Only used to enforce guard is held while calling. - vespalib::LockGuard guard(_queueProcessingLock); + std::lock_guard<std::mutex> guard(_queueProcessingLock); assert(_requestsCurrentlyProcessing > 0); // Full bucket info fetches may be concurrently interleaved with bucket- // specific fetches outside of the processing thread. We only allow queued @@ -529,7 +521,7 @@ BucketManager::processRequestBucketInfoCommands(document::BucketSpace bucketSpac clusterState->toString().c_str(), our_hash.c_str()); - vespalib::LockGuard lock(_clusterStateLock); + std::lock_guard<std::mutex> clusterStateGuard(_clusterStateLock); for (auto it = reqs.rbegin(); it != reqs.rend(); ++it) { // Currently small requests should not be forwarded to worker thread assert((*it)->hasSystemState()); @@ -629,7 +621,7 @@ BucketManager::processRequestBucketInfoCommands(document::BucketSpace bucketSpac size_t BucketManager::bucketInfoRequestsCurrentlyProcessing() const noexcept { - vespalib::LockGuard guard(_queueProcessingLock); + std::lock_guard<std::mutex> guard(_queueProcessingLock); return _requestsCurrentlyProcessing; } @@ -694,7 +686,7 @@ BucketManager::onSetSystemState( LOG(debug, "onSetSystemState(%s)", cmd->toString().c_str()); const lib::ClusterState& state(cmd->getSystemState()); std::string unified(unifyState(state)); - vespalib::LockGuard lock(_clusterStateLock); + std::lock_guard<std::mutex> lock(_clusterStateLock); if (unified != _lastUnifiedClusterState || state.getVersion() != _lastClusterStateSeen + 1) { @@ -804,7 +796,7 @@ BucketManager::enqueueIfBucketHasConflicts(const api::BucketReply::SP& reply) { // Should very rarely contend, since persistence replies are all sent up // via a single dispatcher thread. - vespalib::LockGuard guard(_queueProcessingLock); + std::lock_guard<std::mutex> guard(_queueProcessingLock); if (_requestsCurrentlyProcessing == 0) { return false; // Nothing to do here; pass through reply. } @@ -841,7 +833,7 @@ bool BucketManager::enqueueAsConflictIfProcessingRequest( const api::StorageReply::SP& reply) { - vespalib::LockGuard guard(_queueProcessingLock); + std::lock_guard<std::mutex> guard(_queueProcessingLock); if (_requestsCurrentlyProcessing != 0) { LOG(debug, "Enqueued %s due to concurrent RequestBucketInfo", reply->toString().c_str()); diff --git a/storage/src/vespa/storage/bucketdb/bucketmanager.h b/storage/src/vespa/storage/bucketdb/bucketmanager.h index 3b71230a8ed..8413c5a0f42 100644 --- a/storage/src/vespa/storage/bucketdb/bucketmanager.h +++ b/storage/src/vespa/storage/bucketdb/bucketmanager.h @@ -20,13 +20,14 @@ #include <vespa/storage/common/servicelayercomponent.h> #include <vespa/storage/common/storagelinkqueued.h> #include <vespa/storageapi/message/bucket.h> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageframework/generic/metric/metricupdatehook.h> #include <vespa/storageframework/generic/status/statusreporter.h> #include <list> #include <unordered_map> #include <unordered_set> +#include <mutex> +#include <condition_variable> namespace storage { @@ -45,21 +46,20 @@ private: config::ConfigUri _configUri; uint32_t _chunkLevel; - mutable vespalib::Lock _stateAccess; - framework::MemoryToken::UP _bucketDBMemoryToken; BucketInfoRequestMap _bucketInfoRequests; /** * We have our own thread running, which we use to send messages down. - * Take worker monitor, add to list and signal for messages to be sent. + * Take worker lock, add to list and signal for messages to be sent. */ - mutable vespalib::Monitor _workerMonitor; + mutable std::mutex _workerLock; + std::condition_variable _workerCond; /** * Lock kept for access to 3 values below concerning cluster state. */ - vespalib::Lock _clusterStateLock; + std::mutex _clusterStateLock; - vespalib::Lock _queueProcessingLock; + mutable std::mutex _queueProcessingLock; using ReplyQueue = std::vector<api::StorageReply::SP>; using ConflictingBuckets = std::unordered_set<document::BucketId, document::BucketId::hash>; diff --git a/storage/src/vespa/storage/bucketdb/lockablemap.h b/storage/src/vespa/storage/bucketdb/lockablemap.h index 03d94b27f0b..a4382ceb683 100644 --- a/storage/src/vespa/storage/bucketdb/lockablemap.h +++ b/storage/src/vespa/storage/bucketdb/lockablemap.h @@ -16,10 +16,12 @@ #include <map> #include <vespa/vespalib/util/printable.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vespalib/stllike/hash_map.h> #include <vespa/vespalib/stllike/hash_set.h> #include <vespa/document/bucket/bucketid.h> +#include <mutex> +#include <condition_variable> +#include <cassert> namespace storage { @@ -238,7 +240,8 @@ private: }; Map _map; - vespalib::Monitor _lock; + mutable std::mutex _lock; + std::condition_variable _cond; LockIdSet _lockedKeys; LockWaiters _lockWaiters; @@ -247,9 +250,9 @@ private: const char* clientId, bool haslock, bool& preExisted); void unlock(const key_type& key); bool findNextKey(key_type& key, mapped_type& val, const char* clientId, - vespalib::MonitorGuard& guard); + std::unique_lock<std::mutex> &guard); bool handleDecision(key_type& key, mapped_type& val, Decision decision); - void ackquireKey(const LockId & lid, vespalib::MonitorGuard & guard); + void acquireKey(const LockId & lid, std::unique_lock<std::mutex> &guard); /** * Process up to `chunkSize` bucket database entries from--and possibly @@ -304,7 +307,7 @@ private: void addAndLockResults(const std::vector<BucketId::Type> keys, const char* clientId, std::map<BucketId, WrappedEntry>& results, - vespalib::MonitorGuard& guard); + std::unique_lock<std::mutex> &guard); }; } // storage diff --git a/storage/src/vespa/storage/bucketdb/lockablemap.hpp b/storage/src/vespa/storage/bucketdb/lockablemap.hpp index f5d692139be..f370a792145 100644 --- a/storage/src/vespa/storage/bucketdb/lockablemap.hpp +++ b/storage/src/vespa/storage/bucketdb/lockablemap.hpp @@ -69,6 +69,7 @@ template<typename Map> LockableMap<Map>::LockableMap() : _map(), _lock(), + _cond(), _lockedKeys(), _lockWaiters() {} @@ -80,8 +81,8 @@ template<typename Map> bool LockableMap<Map>::operator==(const LockableMap<Map>& other) const { - vespalib::LockGuard guard(_lock); - vespalib::LockGuard guard2(other._lock); + std::lock_guard<std::mutex> guard(_lock); + std::lock_guard<std::mutex> guard2(other._lock); return (_map == other._map); } @@ -89,8 +90,8 @@ template<typename Map> bool LockableMap<Map>::operator<(const LockableMap<Map>& other) const { - vespalib::LockGuard guard(_lock); - vespalib::LockGuard guard2(other._lock); + std::lock_guard<std::mutex> guard(_lock); + std::lock_guard<std::mutex> guard2(other._lock); return (_map < other._map); } @@ -98,7 +99,7 @@ template<typename Map> typename Map::size_type LockableMap<Map>::size() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _map.size(); } @@ -106,17 +107,16 @@ template<typename Map> typename Map::size_type LockableMap<Map>::getMemoryUsage() const { - vespalib::MonitorGuard guard(_lock); - return _map.getMemoryUsage() - + _lockedKeys.getMemoryUsage() - + sizeof(vespalib::Monitor); + std::lock_guard<std::mutex> guard(_lock); + return _map.getMemoryUsage() + _lockedKeys.getMemoryUsage() + + sizeof(std::mutex) + sizeof(std::condition_variable); } template<typename Map> bool LockableMap<Map>::empty() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _map.empty(); } @@ -124,18 +124,18 @@ template<typename Map> void LockableMap<Map>::swap(LockableMap<Map>& other) { - vespalib::LockGuard guard(_lock); - vespalib::LockGuard guard2(other._lock); + std::lock_guard<std::mutex> guard(_lock); + std::lock_guard<std::mutex> guard2(other._lock); return _map.swap(other._map); } template<typename Map> -void LockableMap<Map>::ackquireKey(const LockId & lid, vespalib::MonitorGuard & guard) +void LockableMap<Map>::acquireKey(const LockId & lid, std::unique_lock<std::mutex> &guard) { if (_lockedKeys.exist(lid)) { typename LockWaiters::Key waitId(_lockWaiters.insert(lid)); while (_lockedKeys.exist(lid)) { - guard.wait(); + _cond.wait(guard); } _lockWaiters.erase(waitId); } @@ -148,8 +148,8 @@ LockableMap<Map>::get(const key_type& key, const char* clientId, bool lockIfNonExistingAndNotCreating) { LockId lid(key, clientId); - vespalib::MonitorGuard guard(_lock); - ackquireKey(lid, guard); + std::unique_lock<std::mutex> guard(_lock); + acquireKey(lid, guard); bool preExisted = false; typename Map::iterator it = _map.find(key, createIfNonExisting, preExisted); @@ -197,9 +197,9 @@ bool LockableMap<Map>::erase(const key_type& key, const char* clientId, bool haslock) { LockId lid(key, clientId); - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); if (!haslock) { - ackquireKey(lid, guard); + acquireKey(lid, guard); } #ifdef ENABLE_BUCKET_OPERATION_LOGGING debug::logBucketDbErase(key, debug::TypeTag<mapped_type>()); @@ -213,9 +213,9 @@ LockableMap<Map>::insert(const key_type& key, const mapped_type& value, const char* clientId, bool haslock, bool& preExisted) { LockId lid(key, clientId); - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); if (!haslock) { - ackquireKey(lid, guard); + acquireKey(lid, guard); } #ifdef ENABLE_BUCKET_OPERATION_LOGGING debug::logBucketDbInsert(key, value); @@ -227,7 +227,7 @@ template<typename Map> void LockableMap<Map>::clear() { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _map.clear(); } @@ -235,13 +235,13 @@ template<typename Map> bool LockableMap<Map>::findNextKey(key_type& key, mapped_type& val, const char* clientId, - vespalib::MonitorGuard& guard) + std::unique_lock<std::mutex> &guard) { // Wait for next value to unlock. typename Map::iterator it(_map.lower_bound(key)); while (it != _map.end() && _lockedKeys.exist(LockId(it->first, ""))) { typename LockWaiters::Key waitId(_lockWaiters.insert(LockId(it->first, clientId))); - guard.wait(); + _cond.wait(guard); _lockWaiters.erase(waitId); it = _map.lower_bound(key); } @@ -279,16 +279,16 @@ LockableMap<Map>::each(Functor& functor, const char* clientId, mapped_type val; Decision decision; { - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); if (findNextKey(key, val, clientId, guard) || key > last) return; _lockedKeys.insert(LockId(key, clientId)); } try{ while (true) { decision = functor(const_cast<const key_type&>(key), val); - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); _lockedKeys.erase(LockId(key, clientId)); - guard.broadcast(); + _cond.notify_all(); if (handleDecision(key, val, decision)) return; ++key; if (findNextKey(key, val, clientId, guard) || key > last) return; @@ -297,9 +297,9 @@ LockableMap<Map>::each(Functor& functor, const char* clientId, } catch (...) { // Assuming only the functor call can throw exceptions, we need // to unlock the current key before exiting - vespalib::MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _lockedKeys.erase(LockId(key, clientId)); - guard.broadcast(); + _cond.notify_all(); throw; } } @@ -314,16 +314,16 @@ LockableMap<Map>::each(const Functor& functor, const char* clientId, mapped_type val; Decision decision; { - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); if (findNextKey(key, val, clientId, guard) || key > last) return; _lockedKeys.insert(LockId(key, clientId)); } try{ while (true) { decision = functor(const_cast<const key_type&>(key), val); - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); _lockedKeys.erase(LockId(key, clientId)); - guard.broadcast(); + _cond.notify_all(); if (handleDecision(key, val, decision)) return; ++key; if (findNextKey(key, val, clientId, guard) || key > last) return; @@ -332,9 +332,9 @@ LockableMap<Map>::each(const Functor& functor, const char* clientId, } catch (...) { // Assuming only the functor call can throw exceptions, we need // to unlock the current key before exiting - vespalib::MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _lockedKeys.erase(LockId(key, clientId)); - guard.broadcast(); + _cond.notify_all(); throw; } } @@ -347,7 +347,7 @@ LockableMap<Map>::all(Functor& functor, const char* clientId, { key_type key = first; mapped_type val; - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); while (true) { if (findNextKey(key, val, clientId, guard) || key > last) return; Decision d(functor(const_cast<const key_type&>(key), val)); @@ -364,7 +364,7 @@ LockableMap<Map>::all(const Functor& functor, const char* clientId, { key_type key = first; mapped_type val; - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); while (true) { if (findNextKey(key, val, clientId, guard) || key > last) return; Decision d(functor(const_cast<const key_type&>(key), val)); @@ -383,7 +383,7 @@ LockableMap<Map>::processNextChunk(Functor& functor, const uint32_t chunkSize) { mapped_type val; - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); for (uint32_t processed = 0; processed < chunkSize; ++processed) { if (findNextKey(key, val, clientId, guard)) { return false; @@ -422,7 +422,7 @@ void LockableMap<Map>::print(std::ostream& out, bool verbose, const std::string& indent) const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); out << "LockableMap {\n" << indent << " "; if (verbose) { @@ -462,9 +462,9 @@ template<typename Map> void LockableMap<Map>::unlock(const key_type& key) { - vespalib::MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _lockedKeys.erase(LockId(key, "")); - guard.broadcast(); + _cond.notify_all(); } /** @@ -550,7 +550,7 @@ LockableMap<Map>::addAndLockResults( const std::vector<BucketId::Type> keys, const char* clientId, std::map<BucketId, WrappedEntry>& results, - vespalib::MonitorGuard& guard) + std::unique_lock<std::mutex> &guard) { // Wait until all buckets are free to be added, then add them all. while (true) { @@ -567,7 +567,7 @@ LockableMap<Map>::addAndLockResults( if (!allOk) { typename LockWaiters::Key waitId(_lockWaiters.insert(LockId(waitingFor, clientId))); - guard.wait(); + _cond.wait(guard); _lockWaiters.erase(waitId); } else { for (uint32_t i=0; i<keys.size(); i++) { @@ -593,7 +593,7 @@ LockableMap<Map>::createAppropriateBucket( const char* clientId, const BucketId& bucket) { - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); typename Map::const_iterator iter = _map.lower_bound(bucket.toKey()); // Find the two buckets around the possible new bucket. The new @@ -613,7 +613,7 @@ LockableMap<Map>::createAppropriateBucket( BucketId::Type key = newBucket.stripUnused().toKey(); LockId lid(key, clientId); - ackquireKey(lid, guard); + acquireKey(lid, guard); bool preExisted; typename Map::iterator it = _map.find(key, true, preExisted); _lockedKeys.insert(LockId(key, clientId)); @@ -625,7 +625,7 @@ std::map<document::BucketId, typename LockableMap<Map>::WrappedEntry> LockableMap<Map>::getContained(const BucketId& bucket, const char* clientId) { - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); std::map<BucketId, WrappedEntry> results; BucketId result; @@ -718,7 +718,7 @@ std::map<document::BucketId, typename LockableMap<Map>::WrappedEntry> LockableMap<Map>::getAll(const BucketId& bucket, const char* clientId, const BucketId& sibling) { - vespalib::MonitorGuard guard(_lock); + std::unique_lock<std::mutex> guard(_lock); std::map<BucketId, WrappedEntry> results; std::vector<BucketId::Type> keys; @@ -734,7 +734,7 @@ template<typename Map> bool LockableMap<Map>::isConsistent(const typename LockableMap<Map>::WrappedEntry& entry) { - vespalib::MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); BucketId sibling(0); std::vector<BucketId::Type> keys; @@ -750,7 +750,7 @@ template<typename Map> void LockableMap<Map>::showLockClients(vespalib::asciistream & out) const { - vespalib::MonitorGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); out << "Currently grabbed locks:"; for (typename LockIdSet::const_iterator it = _lockedKeys.begin(); it != _lockedKeys.end(); ++it) diff --git a/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp b/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp index 5ac4c4b1ee7..ad56a4083f8 100644 --- a/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp +++ b/storage/src/vespa/storage/bucketdb/mapbucketdatabase.cpp @@ -4,6 +4,7 @@ #include <vespa/storage/common/bucketoperationlogger.h> #include <vespa/vespalib/util/backtrace.h> #include <ostream> +#include <cassert> #include <vespa/log/bufferedlogger.h> LOG_SETUP(".mapbucketdatabase"); diff --git a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp index fc2c2066b6f..1bc473308a4 100644 --- a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp +++ b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp @@ -13,11 +13,13 @@ #include <vespa/vespalib/stllike/hash_map.hpp> #include <vespa/config/helper/configgetter.hpp> #include <iomanip> +#include <chrono> #include <vespa/log/bufferedlogger.h> LOG_SETUP(".storage.bucketdb.initializer"); using document::BucketSpace; +using namespace std::chrono_literals; namespace storage { @@ -117,10 +119,11 @@ StorageBucketDBInitializer::Metrics::Metrics(framework::Component& component) StorageBucketDBInitializer::Metrics::~Metrics() {} StorageBucketDBInitializer::GlobalState::GlobalState() - : _insertedCount(0), _infoReadCount(0), - _infoSetByLoad(0), _dirsListed(0), _dirsToList(0), - _gottenInitProgress(false), _doneListing(false), - _doneInitializing(false) + : _lists(), _joins(), _infoRequests(), _replies(), + _insertedCount(0), _infoReadCount(0), + _infoSetByLoad(0), _dirsListed(0), _dirsToList(0), + _gottenInitProgress(false), _doneListing(false), + _doneInitializing(false), _workerLock(), _workerCond(), _replyLock() { } StorageBucketDBInitializer::GlobalState::~GlobalState() { } @@ -183,7 +186,7 @@ void StorageBucketDBInitializer::onClose() { if (_system._thread.get() != 0) { - _system._thread->interruptAndJoin(&_state._workerMonitor); + _system._thread->interruptAndJoin(_state._workerLock, _state._workerCond); _system._thread.reset(0); } } @@ -191,11 +194,11 @@ StorageBucketDBInitializer::onClose() void StorageBucketDBInitializer::run(framework::ThreadHandle& thread) { - vespalib::MonitorGuard monitor(_state._workerMonitor); + std::unique_lock<std::mutex> guard(_state._workerLock); while (!thread.interrupted() && !_state._doneInitializing) { std::list<api::StorageMessage::SP> replies; { - vespalib::LockGuard lock(_state._replyLock); + std::lock_guard<std::mutex> replyGuard(_state._replyLock); _state._replies.swap(replies); } for (std::list<api::StorageMessage::SP>::iterator it = replies.begin(); @@ -218,7 +221,7 @@ StorageBucketDBInitializer::run(framework::ThreadHandle& thread) updateInitProgress(); } if (replies.empty()) { - monitor.wait(10); + _state._workerCond.wait_for(guard, 10ms); thread.registerTick(framework::WAIT_CYCLE); } else { thread.registerTick(framework::PROCESS_CYCLE); @@ -258,7 +261,7 @@ void StorageBucketDBInitializer::reportHtmlStatus( std::ostream& out, const framework::HttpUrlPath&) const { - vespalib::Monitor monitor(_state._workerMonitor); + std::lock_guard<std::mutex> guard(_state._workerLock); out << "\n <h2>Config</h2>\n" << " <table>\n" << " <tr><td>Max pending info reads per disk</td><td>" @@ -583,7 +586,7 @@ StorageBucketDBInitializer::onInternalReply( case ReadBucketInfoReply::ID: case InternalBucketJoinReply::ID: { - vespalib::LockGuard lock(_state._replyLock); + std::lock_guard<std::mutex> guard(_state._replyLock); _state._replies.push_back(reply); return true; } diff --git a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h index 57b95e14f48..642fe43ad8a 100644 --- a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h +++ b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.h @@ -47,11 +47,12 @@ #include <vespa/storageframework/generic/status/htmlstatusreporter.h> #include <vespa/storageframework/generic/clock/timer.h> #include <vespa/vespalib/stllike/hash_map.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/vdslib/state/nodestate.h> #include <vespa/config/subscription/configuri.h> #include <list> #include <unordered_map> +#include <mutex> +#include <condition_variable> namespace storage { @@ -120,9 +121,10 @@ class StorageBucketDBInitializer : public StorageLink, // This lock is held while the worker thread is working, such that // status retrieval can lock it. Listing part only grabs it when // needed to supporting listing in multiple threads - vespalib::Monitor _workerMonitor; + mutable std::mutex _workerLock; + std::condition_variable _workerCond; // This lock protects the reply list. - vespalib::Monitor _replyLock; + std::mutex _replyLock; GlobalState(); ~GlobalState(); diff --git a/storage/src/vespa/storage/common/CMakeLists.txt b/storage/src/vespa/storage/common/CMakeLists.txt index b98058b3c3d..c53aead2ba2 100644 --- a/storage/src/vespa/storage/common/CMakeLists.txt +++ b/storage/src/vespa/storage/common/CMakeLists.txt @@ -6,6 +6,7 @@ vespa_add_library(storage_common OBJECT content_bucket_space.cpp content_bucket_space_repo.cpp distributorcomponent.cpp + global_bucket_space_distribution_converter.cpp messagebucket.cpp messagesender.cpp servicelayercomponent.cpp diff --git a/storage/src/vespa/storage/common/bucketoperationlogger.cpp b/storage/src/vespa/storage/common/bucketoperationlogger.cpp index d6df7f928e2..905b704409f 100644 --- a/storage/src/vespa/storage/common/bucketoperationlogger.cpp +++ b/storage/src/vespa/storage/common/bucketoperationlogger.cpp @@ -34,7 +34,7 @@ BucketOperationLogger::log(const document::BucketId& id, bool hasError = false; { - vespalib::LockGuard lock(_logLock); + std::lock_guard<std:.mutex> guard(_logLock); BucketMapType::iterator i = _bucketMap.lower_bound(id); if (i != _bucketMap.end() && i->first == id) { if (i->second._history.size() >= MAX_ENTRIES) { @@ -145,7 +145,7 @@ void BucketOperationLogger::dumpHistoryToLog(const document::BucketId& id) const { LogWarnAppender handler; - vespalib::LockGuard lock(_logLock); + std::lock_guard<std::mutex> guard(_logLock); processHistory(*this, id, handler); } @@ -153,7 +153,7 @@ vespalib::string BucketOperationLogger::getHistory(const document::BucketId& id) const { LogStringBuilder handler; - vespalib::LockGuard lock(_logLock); + std::lock_guard<std::mutex> lock(_logLock); processHistory(*this, id, handler); return handler.ss.str(); } @@ -167,7 +167,7 @@ BucketOperationLogger::searchBucketHistories( ss << "<ul>\n"; // This may block for a while... Assuming such searches run when system // is otherwise idle. - vespalib::LockGuard lock(_logLock); + std::lock_guard<std::mutex> guard(_logLock); for (BucketMapType::const_iterator bIt(_bucketMap.begin()), bEnd(_bucketMap.end()); bIt != bEnd; ++bIt) diff --git a/storage/src/vespa/storage/common/bucketoperationlogger.h b/storage/src/vespa/storage/common/bucketoperationlogger.h index dce9334a9cf..af4b539a4c8 100644 --- a/storage/src/vespa/storage/common/bucketoperationlogger.h +++ b/storage/src/vespa/storage/common/bucketoperationlogger.h @@ -2,10 +2,10 @@ #pragma once #include <vespa/vespalib/stllike/string.h> -#include <vespa/vespalib/util/sync.h> #include <vespa/document/bucket/bucketid.h> #include <map> #include <list> +#include <mutex> /** * Enable this to log most slotfile operations (such as all mutations) as @@ -85,7 +85,7 @@ struct BucketOperationLogger typedef std::map<document::BucketId, State> BucketMapType; - vespalib::Lock _logLock; + std::mutex _logLock; BucketMapType _bucketMap; void log(const document::BucketId& id, diff --git a/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp new file mode 100644 index 00000000000..d8a3dd4780f --- /dev/null +++ b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.cpp @@ -0,0 +1,157 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "global_bucket_space_distribution_converter.h" +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/vdslib/distribution/distribution_config_util.h> +#include <vespa/vespalib/stllike/asciistream.h> +#include <cassert> +#include <map> +#include <memory> + +namespace storage { + +using DistributionConfig = vespa::config::content::StorDistributionConfig; +using DistributionConfigBuilder = vespa::config::content::StorDistributionConfigBuilder; + +namespace { + +struct Group { + uint16_t nested_leaf_count{0}; + std::map<uint16_t, std::unique_ptr<Group>> sub_groups; +}; + +void set_distribution_invariant_config_fields(DistributionConfigBuilder& builder, const DistributionConfig& source) { + builder.diskDistribution = source.diskDistribution; + builder.distributorAutoOwnershipTransferOnWholeGroupDown = true; + builder.activePerLeafGroup = true; + // TODO consider how to best support n-of-m replication for global docs + builder.ensurePrimaryPersisted = true; + builder.initialRedundancy = 0; +} + +const Group& find_non_root_group_by_index(const vespalib::string& index, const Group& root) { + auto path = lib::DistributionConfigUtil::getGroupPath(index); + auto* node = &root; + for (auto idx : path) { + auto child_iter = node->sub_groups.find(idx); + assert(child_iter != node->sub_groups.end()); + node = child_iter->second.get(); + } + return *node; +} + +vespalib::string sub_groups_to_partition_spec(const Group& parent) { + vespalib::asciistream partitions; + // In case of a flat cluster config, this ends up with a partition spec of '*', + // which is fine. It basically means "put all replicas in this group", which + // happens to be exactly what we want. + for (auto& child : parent.sub_groups) { + partitions << child.second->nested_leaf_count << '|'; + } + partitions << '*'; + return partitions.str(); +} + +bool is_leaf_group(const DistributionConfigBuilder::Group& g) noexcept { + return !g.nodes.empty(); +} + +void insert_new_group_into_tree( + std::unique_ptr<Group> new_group, + const DistributionConfigBuilder::Group& config_source_group, + Group& root) { + const auto path = lib::DistributionConfigUtil::getGroupPath(config_source_group.index); + assert(!path.empty()); + + Group* parent = &root; + for (size_t i = 0; i < path.size(); ++i) { + const auto idx = path[i]; + parent->nested_leaf_count += config_source_group.nodes.size(); // Empty if added group is not a leaf. + auto g_iter = parent->sub_groups.find(idx); + if (g_iter != parent->sub_groups.end()) { + assert(i != path.size() - 1); + parent = g_iter->second.get(); + } else { + assert(i == path.size() - 1); // Only valid case for last item in path. + parent->sub_groups.emplace(path.back(), std::move(new_group)); + } + } +} + +void build_transformed_root_group(DistributionConfigBuilder& builder, + const DistributionConfigBuilder::Group& config_source_root, + const Group& parsed_root) { + DistributionConfigBuilder::Group new_root(config_source_root); + new_root.partitions = sub_groups_to_partition_spec(parsed_root); + builder.group.emplace_back(std::move(new_root)); +} + +void build_transformed_non_root_group(DistributionConfigBuilder& builder, + const DistributionConfigBuilder::Group& config_source_group, + const Group& parsed_root) { + DistributionConfigBuilder::Group new_group(config_source_group); + if (!is_leaf_group(config_source_group)) { // Partition specs only apply to inner nodes + const auto& g = find_non_root_group_by_index(config_source_group.index, parsed_root); + new_group.partitions = sub_groups_to_partition_spec(g); + } + builder.group.emplace_back(std::move(new_group)); +} + +std::unique_ptr<Group> create_group_tree_from_config(const DistributionConfig& source) { + std::unique_ptr<Group> root; + for (auto& g : source.group) { + auto new_group = std::make_unique<Group>(); + assert(g.nodes.size() < UINT16_MAX); + new_group->nested_leaf_count = static_cast<uint16_t>(g.nodes.size()); + if (root) { + insert_new_group_into_tree(std::move(new_group), g, *root); + } else { + root = std::move(new_group); + } + } + return root; +} + +/* Even though groups are inherently hierarchical, the config is a flat array with a + * hierarchy bolted on through the use of (more or less) "multi-dimensional" index strings. + * Index string of root group is always "invalid" (or possibly some other string that cannot + * be interpreted as a dot-separated tree node path). Other groups have an index of the + * form "X.Y.Z", where Z is the group's immediate parent index, Y is Z's parent and so on. Just + * stating Z itself is not sufficient to uniquely identify the group, as group indices are + * not unique _across_ groups. For indices "0.1" and "1.1", the trailing "1" refers to 2 + * distinct groups, as they have different parents. + * + * It may be noted that the group index strings do _not_ include the root group, so we + * have to always implicitly include it ourselves. + * + * Config groups are ordered so that when a group is encountered, all its parents (and + * transitively, its parents again etc) have already been processed. This directly + * implies that the root group is always the first group present in the config. + */ +void build_global_groups(DistributionConfigBuilder& builder, const DistributionConfig& source) { + assert(!source.group.empty()); // TODO gracefully handle empty config? + auto root = create_group_tree_from_config(source); + + auto g_iter = source.group.begin(); + const auto g_end = source.group.end(); + build_transformed_root_group(builder, *g_iter, *root); + ++g_iter; + for (; g_iter != g_end; ++g_iter) { + build_transformed_non_root_group(builder, *g_iter, *root); + } + + builder.redundancy = root->nested_leaf_count; + builder.readyCopies = builder.redundancy; +} + +} // anon ns + +std::shared_ptr<DistributionConfig> +GlobalBucketSpaceDistributionConverter::convert_to_global(const DistributionConfig& source) { + DistributionConfigBuilder builder; + set_distribution_invariant_config_fields(builder, source); + build_global_groups(builder, source); + return std::make_shared<DistributionConfig>(builder); +} + +}
\ No newline at end of file diff --git a/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h new file mode 100644 index 00000000000..32a43b3081e --- /dev/null +++ b/storage/src/vespa/storage/common/global_bucket_space_distribution_converter.h @@ -0,0 +1,16 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vdslib/distribution/distribution.h> +#include <vespa/config-stor-distribution.h> +#include <memory> + +namespace storage { + +struct GlobalBucketSpaceDistributionConverter { + using DistributionConfig = vespa::config::content::StorDistributionConfig; + static std::shared_ptr<DistributionConfig> convert_to_global(const DistributionConfig&); +}; + +}
\ No newline at end of file diff --git a/storage/src/vespa/storage/common/statusmetricconsumer.cpp b/storage/src/vespa/storage/common/statusmetricconsumer.cpp index de44116b316..d458a821e02 100644 --- a/storage/src/vespa/storage/common/statusmetricconsumer.cpp +++ b/storage/src/vespa/storage/common/statusmetricconsumer.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 "statusmetricconsumer.h" -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageframework/generic/status/htmlstatusreporter.h> #include <boost/assign.hpp> #include <boost/lexical_cast.hpp> @@ -26,17 +25,8 @@ StatusMetricConsumer::StatusMetricConsumer( _component(compReg, "statusmetricsconsumer"), _name(name), _startTime(_component.getClock().getTimeInSeconds()), - _processedTime(0), - _metricMemoryToken() + _processedTime(0) { - const framework::MemoryAllocationType& allocType( - _component.getMemoryManager().registerAllocationType( - framework::MemoryAllocationType( - "METRICS", framework::MemoryAllocationType::FORCE_ALLOCATE) - )); - _metricMemoryToken = _component.getMemoryManager().allocate( - allocType, 0, 0, api::StorageMessage::HIGH); - assert(_metricMemoryToken.get() != 0); LOG(debug, "Started metrics consumer"); setlocale(LC_NUMERIC, ""); _component.registerMetricUpdateHook(*this, framework::SecondTime(3600)); @@ -51,8 +41,7 @@ void StatusMetricConsumer::updateMetrics(const MetricLockGuard & guard) { metrics::MemoryConsumption::UP mc(_manager.getMemoryConsumption(guard)); - uint32_t usage = mc->getTotalMemoryUsage(); - _metricMemoryToken->resize(usage, usage); + // TODO is this hook needed anymore? } vespalib::string diff --git a/storage/src/vespa/storage/common/statusmetricconsumer.h b/storage/src/vespa/storage/common/statusmetricconsumer.h index e3bae51cf99..6f93f51cfdf 100644 --- a/storage/src/vespa/storage/common/statusmetricconsumer.h +++ b/storage/src/vespa/storage/common/statusmetricconsumer.h @@ -53,7 +53,6 @@ private: vespalib::Monitor _waiter; framework::SecondTime _startTime; framework::SecondTime _processedTime; - std::unique_ptr<framework::MemoryToken> _metricMemoryToken; void writeXmlTags(std::ostream& out, const vespalib::StringTokenizer& name, diff --git a/storage/src/vespa/storage/common/storagecomponent.cpp b/storage/src/vespa/storage/common/storagecomponent.cpp index a519e39e2ed..bf387240dc5 100644 --- a/storage/src/vespa/storage/common/storagecomponent.cpp +++ b/storage/src/vespa/storage/common/storagecomponent.cpp @@ -28,14 +28,14 @@ StorageComponent::setNodeInfo(vespalib::stringref clusterName, void StorageComponent::setDocumentTypeRepo(DocumentTypeRepoSP repo) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _docTypeRepo = repo; } void StorageComponent::setLoadTypes(LoadTypeSetSP loadTypes) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _loadTypes = loadTypes; } @@ -57,14 +57,14 @@ StorageComponent::setBucketIdFactory(const document::BucketIdFactory& factory) void StorageComponent::setDistribution(DistributionSP distribution) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); _distribution = distribution; } void StorageComponent::setNodeStateUpdater(NodeStateUpdater& updater) { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); if (_nodeStateUpdater != 0) { throw vespalib::IllegalStateException( "Node state updater is already set", VESPA_STRLOC); @@ -87,7 +87,7 @@ StorageComponent::StorageComponent(StorageComponentRegister& compReg, NodeStateUpdater& StorageComponent::getStateUpdater() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); if (_nodeStateUpdater == 0) { throw vespalib::IllegalStateException( "Component need node state updater at this time, but it has " @@ -114,21 +114,21 @@ StorageComponent::getPriority(const documentapi::LoadType& lt) const StorageComponent::DocumentTypeRepoSP StorageComponent::getTypeRepo() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _docTypeRepo; } StorageComponent::LoadTypeSetSP StorageComponent::getLoadTypes() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _loadTypes; } StorageComponent::DistributionSP StorageComponent::getDistribution() const { - vespalib::LockGuard guard(_lock); + std::lock_guard<std::mutex> guard(_lock); return _distribution; } diff --git a/storage/src/vespa/storage/common/storagecomponent.h b/storage/src/vespa/storage/common/storagecomponent.h index f34b6ace745..d469540b55f 100644 --- a/storage/src/vespa/storage/common/storagecomponent.h +++ b/storage/src/vespa/storage/common/storagecomponent.h @@ -35,7 +35,7 @@ #include <vespa/storageframework/generic/component/componentregister.h> #include <vespa/document/bucket/bucketidfactory.h> #include <vespa/vdslib/state/node.h> -#include <vespa/vespalib/util/sync.h> +#include <mutex> namespace vespa { namespace config { namespace content { namespace core { namespace internal { @@ -113,7 +113,7 @@ private: document::BucketIdFactory _bucketIdFactory; DistributionSP _distribution; NodeStateUpdater* _nodeStateUpdater; - vespalib::Lock _lock; + mutable std::mutex _lock; }; struct StorageComponentRegister : public virtual framework::ComponentRegister diff --git a/storage/src/vespa/storage/common/storagelinkqueued.h b/storage/src/vespa/storage/common/storagelinkqueued.h index b7891cc48dc..ee5573bcc22 100644 --- a/storage/src/vespa/storage/common/storagelinkqueued.h +++ b/storage/src/vespa/storage/common/storagelinkqueued.h @@ -19,6 +19,8 @@ #include <vespa/vespalib/util/document_runnable.h> #include <deque> #include <limits> +#include <mutex> +#include <condition_variable> namespace storage { @@ -75,7 +77,8 @@ private: protected: StorageLinkQueued& _parent; unsigned int _maxQueueSize; - vespalib::Monitor _sync; + std::mutex _sync; + std::condition_variable _syncCond; std::deque< std::shared_ptr<Message> > _messages; bool _replyDispatcher; std::unique_ptr<framework::Component> _component; @@ -92,11 +95,6 @@ private: void add(const std::shared_ptr<Message>&); void flush(); - // You can use the given functions if you need to keep the - // dispatcher thread locked while you process a message. Bucket - // manager does this during bucket dumps - vespalib::Monitor& getMonitor() { return _sync; } - void addWithoutLocking(const std::shared_ptr<Message>&); virtual void send(const std::shared_ptr<Message> & ) = 0; }; diff --git a/storage/src/vespa/storage/common/storagelinkqueued.hpp b/storage/src/vespa/storage/common/storagelinkqueued.hpp index b34ce58bbb7..22c5e9ba5f2 100644 --- a/storage/src/vespa/storage/common/storagelinkqueued.hpp +++ b/storage/src/vespa/storage/common/storagelinkqueued.hpp @@ -7,6 +7,7 @@ #include <vespa/storageframework/generic/component/component.h> #include <vespa/vespalib/util/stringfmt.h> #include <sstream> +#include <chrono> namespace storage { @@ -16,8 +17,8 @@ StorageLinkQueued::Dispatcher<Message>::terminate() { if (_thread.get()) { _thread->interrupt(); { - vespalib::MonitorGuard sync(_sync); - sync.signal(); + std::lock_guard<std::mutex> guard(_sync); + _syncCond.notify_one(); } _thread->join(); _thread.reset(0); @@ -29,6 +30,7 @@ StorageLinkQueued::Dispatcher<Message>::Dispatcher(StorageLinkQueued& parent, un : _parent(parent), _maxQueueSize(maxQueueSize), _sync(), + _syncCond(), _messages(), _replyDispatcher(replyDispatcher) { @@ -58,34 +60,28 @@ template<typename Message> void StorageLinkQueued::Dispatcher<Message>::add( const std::shared_ptr<Message>& m) { - vespalib::MonitorGuard sync(_sync); + using namespace std::chrono_literals; + std::unique_lock<std::mutex> guard(_sync); if (_thread.get() == 0) start(); while ((_messages.size() > _maxQueueSize) && !_thread->interrupted()) { - sync.wait(100); + _syncCond.wait_for(guard, 100ms); } _messages.push_back(m); - sync.signal(); -} - -template<typename Message> -void StorageLinkQueued::Dispatcher<Message>::addWithoutLocking( - const std::shared_ptr<Message>& m) -{ - if (_thread.get() == 0) start(); - _messages.push_back(m); + _syncCond.notify_one(); } template<typename Message> void StorageLinkQueued::Dispatcher<Message>::run(framework::ThreadHandle& h) { + using namespace std::chrono_literals; while (!h.interrupted()) { h.registerTick(framework::PROCESS_CYCLE); std::shared_ptr<Message> message; { - vespalib::MonitorGuard sync(_sync); + std::unique_lock<std::mutex> guard(_sync); while (!h.interrupted() && _messages.empty()) { - sync.wait(100); + _syncCond.wait_for(guard, 100ms); h.registerTick(framework::WAIT_CYCLE); } if (h.interrupted()) break; @@ -104,9 +100,9 @@ void StorageLinkQueued::Dispatcher<Message>::run(framework::ThreadHandle& h) { // Since flush() only waits for stack to be empty, we must // pop stack AFTER send have been called. - vespalib::MonitorGuard sync(_sync); + std::lock_guard<std::mutex> guard(_sync); _messages.pop_front(); - sync.signal(); + _syncCond.notify_one(); } } _parent.logDebug("Finished storage link queued thread"); @@ -115,9 +111,10 @@ void StorageLinkQueued::Dispatcher<Message>::run(framework::ThreadHandle& h) template<typename Message> void StorageLinkQueued::Dispatcher<Message>::flush() { - vespalib::MonitorGuard sync(_sync); + using namespace std::chrono_literals; + std::unique_lock<std::mutex> guard(_sync); while (!_messages.empty()) { - sync.wait(100); + _syncCond.wait_for(guard, 100ms); } } diff --git a/storage/src/vespa/storage/config/.gitignore b/storage/src/vespa/storage/config/.gitignore index 621aa4f624a..5d9aa323622 100644 --- a/storage/src/vespa/storage/config/.gitignore +++ b/storage/src/vespa/storage/config/.gitignore @@ -9,3 +9,5 @@ config-stor-*.cpp config-stor-*.h /config-rpc-provider.cpp /config-rpc-provider.h +/config-bucketspaces.cpp +/config-bucketspaces.h diff --git a/storage/src/vespa/storage/config/bucketspaces.def b/storage/src/vespa/storage/config/bucketspaces.def index 3ed1abba0b4..4db107ec1ee 100644 --- a/storage/src/vespa/storage/config/bucketspaces.def +++ b/storage/src/vespa/storage/config/bucketspaces.def @@ -9,3 +9,6 @@ documenttype[].name string ## The bucket space this document type belongs to. documenttype[].bucketspace string + +## Switch to enable multiple bucket spaces in content layer and content nodes. +enable_multiple_bucket_spaces bool default=false diff --git a/storage/src/vespa/storage/distributor/bucketdbupdater.h b/storage/src/vespa/storage/distributor/bucketdbupdater.h index a3c9804c2b4..29e8d3f6221 100644 --- a/storage/src/vespa/storage/distributor/bucketdbupdater.h +++ b/storage/src/vespa/storage/distributor/bucketdbupdater.h @@ -13,7 +13,6 @@ #include <vespa/vdslib/state/clusterstate.h> #include <vespa/storage/common/storagelink.h> #include <vespa/storageframework/generic/clock/timer.h> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageapi/messageapi/messagehandler.h> #include <set> #include <deque> diff --git a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h index e8480902549..8d31a228543 100644 --- a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h +++ b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h @@ -6,7 +6,6 @@ #include <vespa/storageapi/messageapi/storagereply.h> #include <vespa/storageapi/messageapi/maintenancecommand.h> #include <vespa/document/bucket/bucketid.h> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> namespace storage::distributor { @@ -229,7 +228,6 @@ protected: bool _ok; api::StorageMessage::Priority _priority; - framework::MemoryToken::UP _memoryToken; /** * Checks if the given bucket is blocked by any pending messages to any diff --git a/storage/src/vespa/storage/frameworkimpl/memory/CMakeLists.txt b/storage/src/vespa/storage/frameworkimpl/memory/CMakeLists.txt deleted file mode 100644 index 2a91ead2b60..00000000000 --- a/storage/src/vespa/storage/frameworkimpl/memory/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(storage_memory OBJECT - SOURCES - memorystatusviewer.cpp - DEPENDS - AFTER - storage_storageconfig -) diff --git a/storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h b/storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h deleted file mode 100644 index fd47bec1424..00000000000 --- a/storage/src/vespa/storage/frameworkimpl/memory/memorysnapshotlist.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::MemorySnapshotList - * - * \brief Holds a historic list of MemoryStates. - * - */ - -#pragma once - -#include <storage/frameworkimpl/memory/memorystate.h> - -namespace storage { - -class MemorySnapshotList : public vespalib::Printable { - std::map<uint64_t time, MemoryState> _snapshots; -}; - -} // storage - diff --git a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp b/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp deleted file mode 100644 index dd07a61f27b..00000000000 --- a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.cpp +++ /dev/null @@ -1,661 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "memorystatusviewer.h" -#include <vespa/storage/storageutil/graph.h> -#include <vespa/storage/storageutil/palette.h> -#include <vespa/storage/storageutil/piechart.h> -#include <vespa/metrics/metricmanager.h> -#include <vespa/storageapi/messageapi/storagemessage.h> -#include <algorithm> - -#include <vespa/log/bufferedlogger.h> -LOG_SETUP(".memory.status.viewer"); - -using storage::framework::defaultimplementation::MemoryState; - -namespace storage { - -MemoryStatusViewer::Entry::Entry( - const std::string& name, framework::Clock& clock, - framework::SecondTime maxAge) - : _name(name), - _maxAge(maxAge), - _timeTaken(clock.getTimeInSeconds()), - _data(), - _maxMemory(0) -{ -} - -MemoryStatusViewer::MemoryStatusViewer( - framework::defaultimplementation::MemoryManager& mm, - const metrics::MetricManager& metricMan, - StorageComponentRegister& compReg) - : framework::HtmlStatusReporter("memorymanager", "Memory Manager"), - _component(compReg, "memorystatusviewer"), - _manager(mm), - _metricManager(metricMan), - _workerMonitor(), - _states(), - _memoryHistory(), - _memoryHistorySize(24 * 31), - _memoryHistoryPeriod(60), - _allowedSlackPeriod(6), - _lastHistoryUpdate(_component.getClock().getTimeInSeconds()), - _processedTime(0) -{ - addEntry("Current", 0); - addEntry("Last hour", 60 * 60); - addEntry("Last day", 24 * 60 * 60); - addEntry("Last month", 4 * 7 * 24 * 60 * 60); - addEntry("Last ever", std::numeric_limits<uint32_t>::max()); - - framework::MilliSecTime maxProcessingTime(60 * 1000); - _thread = _component.startThread(*this, maxProcessingTime, - framework::MilliSecTime(1000)); - _component.registerStatusPage(*this); -} - -MemoryStatusViewer::~MemoryStatusViewer() -{ - if (_thread.get() != 0) { - _thread->interrupt(); - { - vespalib::MonitorGuard monitor(_workerMonitor); - monitor.signal(); - } - _thread->join(); - } -} - -namespace { - struct Group { - std::set<const framework::MemoryAllocationType*> types; - api::StorageMessage::Priority minPri; - api::StorageMessage::Priority maxPri; - MemoryState::Entry entry; - - Group(const framework::MemoryAllocationType& type, - api::StorageMessage::Priority pri, - const MemoryState::Entry& e) - : types(), - minPri(pri), - maxPri(pri), - entry(e) - { - types.insert(&type); - } - }; - - struct GroupSizeOrder { - bool operator()(const Group& g1, const Group& g2) { - return (g1.entry._currentUsedSize > g2.entry._currentUsedSize); - } - }; - struct GroupAllocsOrder { - bool operator()(const Group& g1, const Group& g2) { - return (g1.entry._totalUserCount > g2.entry._totalUserCount); - } - }; - struct GroupMinAllocsOrder { - bool operator()(const Group& g1, const Group& g2) { - return (g1.entry._minimumCount > g2.entry._minimumCount); - } - }; - struct GroupDeniedAllocsOrder { - bool operator()(const Group& g1, const Group& g2) { - return (g1.entry._deniedCount > g2.entry._deniedCount); - } - }; - - std::vector<Group> collapsePriorities(MemoryStatusViewer::Entry& entry) { - std::vector<Group> groups; - const MemoryState::SnapShot& ss(entry._data); - for (MemoryState::AllocationMap::const_iterator it - = ss.getAllocations().begin(); - it != ss.getAllocations().end(); ++it) - { - std::unique_ptr<Group> group; - for (MemoryState::PriorityMap::const_iterator it2 - = it->second.begin(); it2 != it->second.end(); ++it2) - { - if (group.get() == 0) { - group.reset(new Group( - *it->first, it2->first, it2->second)); - } else { - group->entry += it2->second; - group->minPri = std::min(group->minPri, it2->first); - group->maxPri = std::max(group->maxPri, it2->first); - } - } - if (group.get() != 0) { - groups.push_back(*group); - } - } - return groups; - } - - std::vector<Group> groupLoad(uint32_t groupCount, uint64_t minSize, - uint32_t minEntries, - MemoryStatusViewer::Entry& entry) - { - assert(groupCount > 1); - std::vector<Group> groups(collapsePriorities(entry)); - if (groups.size() == 0) return groups; - std::sort(groups.begin(), groups.end(), GroupSizeOrder()); - assert(groups.front().entry._currentUsedSize - >= groups.back().entry._currentUsedSize); - while (groups.size() > minEntries - && (groups.size() > groupCount - || groups[groups.size() - 2].entry._currentUsedSize - < minSize)) - { - Group& nextButLast(groups[groups.size() - 2]); - Group& last(groups.back()); - if (last.entry._currentUsedSize > 0) { - nextButLast.entry += last.entry; - nextButLast.minPri = std::min(nextButLast.minPri, last.minPri); - nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri); - nextButLast.types.insert(*last.types.begin()); - } - groups.pop_back(); - } - return groups; - } - - std::vector<Group> groupAllocs(uint32_t groupCount, uint64_t minSize, - uint32_t minEntries, - MemoryStatusViewer::Entry& entry) - { - assert(groupCount > 1); - std::vector<Group> groups(collapsePriorities(entry)); - if (groups.size() == 0) return groups; - std::sort(groups.begin(), groups.end(), GroupAllocsOrder()); - assert(groups.front().entry._totalUserCount - >= groups.back().entry._totalUserCount); - while (groups.size() > minEntries - && (groups.size() > groupCount - || groups[groups.size() - 2].entry._totalUserCount - < minSize)) - { - Group& nextButLast(groups[groups.size() - 2]); - Group& last(groups.back()); - nextButLast.entry += last.entry; - nextButLast.minPri = std::min(nextButLast.minPri, last.minPri); - nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri); - nextButLast.types.insert(*last.types.begin()); - groups.pop_back(); - } - return groups; - } - - std::vector<Group> groupMinAllocs(uint32_t groupCount, uint64_t minSize, - uint32_t minEntries, - MemoryStatusViewer::Entry& entry) - { - assert(groupCount > 1); - std::vector<Group> groups(collapsePriorities(entry)); - if (groups.size() == 0) return groups; - std::sort(groups.begin(), groups.end(), GroupMinAllocsOrder()); - assert(groups.front().entry._minimumCount - >= groups.back().entry._minimumCount); - while (groups.size() > minEntries - && (groups.size() > groupCount - || groups[groups.size() - 2].entry._minimumCount - < minSize)) - { - Group& nextButLast(groups[groups.size() - 2]); - Group& last(groups.back()); - nextButLast.entry += last.entry; - nextButLast.minPri = std::min(nextButLast.minPri, last.minPri); - nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri); - nextButLast.types.insert(*last.types.begin()); - groups.pop_back(); - } - return groups; - } - - std::vector<Group> groupDeniedAllocs(uint32_t groupCount, uint64_t minSize, - uint32_t minEntries, - MemoryStatusViewer::Entry& entry) - { - assert(groupCount > 1); - std::vector<Group> groups(collapsePriorities(entry)); - if (groups.size() == 0) return groups; - std::sort(groups.begin(), groups.end(), GroupDeniedAllocsOrder()); - assert(groups.front().entry._deniedCount - >= groups.back().entry._deniedCount); - while (groups.size() > minEntries - && (groups.size() > groupCount - || groups[groups.size() - 2].entry._deniedCount - < minSize)) - { - Group& nextButLast(groups[groups.size() - 2]); - Group& last(groups.back()); - nextButLast.entry += last.entry; - nextButLast.minPri = std::min(nextButLast.minPri, last.minPri); - nextButLast.maxPri = std::max(nextButLast.maxPri, last.maxPri); - nextButLast.types.insert(*last.types.begin()); - groups.pop_back(); - } - return groups; - } -} - -void -MemoryStatusViewer::printSnapshot( - std::ostream& out, Entry& entry, - std::map<const framework::MemoryAllocationType*, - uint32_t>& colors) const -{ - out << "<h4>" << entry._name << " - Taken at " - << entry._timeTaken.toString() << "</h4>\n" - << "<table><tr><td>\n" - << "<b>Memory usage"; - if (entry._name != "Current") { - out << ", maxed at " << framework::SecondTime(entry._timeTaken); - } - out << " with " - << (entry._data.getUsedSizeIgnoringCache() / (1024 * 1024)) - << " MB.</b><br>\n"; - std::string piename = entry._name; - std::replace(piename.begin(), piename.end(), ' ', '_'); - uint64_t freeSize = entry._maxMemory - entry._data.getUsedSize(); - // Memory usage pie - uint64_t minSize = freeSize / 20; - std::vector<Group> groups(groupLoad(20, minSize, 5, entry)); - PieChart chart(piename, PieChart::SCHEME_CUSTOM); - chart.printLabels(false); - for (uint32_t i=0; i<groups.size(); ++i) { - std::string name = "Other"; - if (groups[i].types.size() == 1) { - name = (*groups[i].types.begin())->getName(); - } - uint32_t mbytes = groups[i].entry._currentUsedSize / (1024 * 1024); - std::ostringstream ost; - ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri); - if (groups[i].minPri != groups[i].maxPri) { - ost << " - " << static_cast<uint16_t>(groups[i].maxPri); - } - ost << " (" << mbytes << " MB)"; - name = ost.str(); - if (groups[i].entry._currentUsedSize > 0) { - chart.add(groups[i].entry._currentUsedSize, name, - colors[*groups[i].types.begin()]); - } - } - { - std::ostringstream ost; - ost << "Free (" << (freeSize / (1024 * 1024)) << " MB)"; - chart.add(freeSize, ost.str(), colors[0]); - } - chart.printCanvas(out, 750, 300); - out << "\n\n"; - chart.printScript(out, ""); - out << "\n\n"; - // Total allocations pie - out << "</td><td>\n"; - PieChart allocChart(piename + "Alloc", PieChart::SCHEME_CUSTOM); - allocChart.printLabels(false); - groups = groupAllocs(20, 100, 5, entry); - uint64_t totalAllocs = 0; - for (uint32_t i=0; i<groups.size(); ++i) { - std::string name = "Other"; - if (groups[i].types.size() == 1) { - name = (*groups[i].types.begin())->getName(); - } - uint32_t allocs = groups[i].entry._totalUserCount; - totalAllocs += allocs; - std::ostringstream ost; - ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri); - if (groups[i].minPri != groups[i].maxPri) { - ost << " - " << static_cast<uint16_t>(groups[i].maxPri); - } - ost << " (" << allocs << " allocations)"; - name = ost.str(); - if (groups[i].entry._totalUserCount > 0) { - allocChart.add(groups[i].entry._totalUserCount, name, - colors[*groups[i].types.begin()]); - } - } - out << "<b>Allocations, totalling " << totalAllocs << "</b><br>\n"; - allocChart.printCanvas(out, 750, 300); - out << "\n\n"; - allocChart.printScript(out, ""); - out << "\n\n"; - out << "</td></tr><tr><td>\n"; - PieChart minChart(piename + "Min", PieChart::SCHEME_CUSTOM); - minChart.printLabels(false); - groups = groupMinAllocs(20, 100, 5, entry); - uint64_t totalMinAllocs = 0; - for (uint32_t i=0; i<groups.size(); ++i) { - std::string name = "Other"; - if (groups[i].types.size() == 1) { - name = (*groups[i].types.begin())->getName(); - } - uint32_t allocs = groups[i].entry._minimumCount; - totalMinAllocs += allocs; - std::ostringstream ost; - ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri); - if (groups[i].minPri != groups[i].maxPri) { - ost << " - " << static_cast<uint16_t>(groups[i].maxPri); - } - ost << " (" << allocs << " min allocations)"; - name = ost.str(); - if (groups[i].entry._minimumCount > 0) { - minChart.add(groups[i].entry._minimumCount, name, - colors[*groups[i].types.begin()]); - } - } - out << "<b>Minimum allocations, totalling " << totalMinAllocs - << "</b><br>\n"; - if (totalMinAllocs > 0) { - minChart.printCanvas(out, 750, 300); - out << "\n\n"; - minChart.printScript(out, ""); - out << "\n\n"; - } - out << "</td><td>\n"; - PieChart deniedChart(piename + "Denied", PieChart::SCHEME_CUSTOM); - deniedChart.printLabels(false); - groups = groupDeniedAllocs(20, 100, 5, entry); - uint64_t totalDeniedAllocs = 0; - for (uint32_t i=0; i<groups.size(); ++i) { - std::string name = "Other"; - if (groups[i].types.size() == 1) { - name = (*groups[i].types.begin())->getName(); - } - uint32_t allocs = groups[i].entry._deniedCount; - totalDeniedAllocs += allocs; - std::ostringstream ost; - ost << name << ", pri " << static_cast<uint16_t>(groups[i].minPri); - if (groups[i].minPri != groups[i].maxPri) { - ost << " - " << static_cast<uint16_t>(groups[i].maxPri); - } - ost << " (" << allocs << " denied allocations)"; - name = ost.str(); - if (groups[i].entry._deniedCount > 0) { - deniedChart.add(groups[i].entry._deniedCount, name, - colors[*groups[i].types.begin()]); - } - } - out << "<b>Denied allocations, totalling " << totalDeniedAllocs - << "</b><br>\n"; - if (totalDeniedAllocs > 0) { - deniedChart.printCanvas(out, 750, 300); - out << "\n\n"; - deniedChart.printScript(out, ""); - out << "\n\n"; - } - out << "</td></tr></table>\n"; -} - -void -MemoryStatusViewer::reportHtmlHeaderAdditions( - std::ostream& out, const framework::HttpUrlPath&) const -{ - (void) out; - // FIXME this function used to emit Yahoo-internal links to graph plotting - // JS files. Obviously, this won't work for external users. Either way, the - // memory manager/status reporter is deprecated. -} - -namespace { - std::map<const framework::MemoryAllocationType*, uint32_t> assignColors( - const std::vector<const framework::MemoryAllocationType*>& types) - { - Palette palette(types.size() + 1); - std::map<const framework::MemoryAllocationType*, uint32_t> colors; - uint32_t nextCol = 0; - colors[0] = palette[nextCol++]; - for (std::vector<const framework::MemoryAllocationType*> - ::const_iterator it = types.begin(); it != types.end(); ++it) - { - colors[*it] = palette[nextCol++]; - } - return colors; - } -} - -void -MemoryStatusViewer::reportHtmlStatus(std::ostream& out, - const framework::HttpUrlPath& path) const -{ - vespalib::MonitorGuard monitor(_workerMonitor); - - if (path.getAttribute("page") == "reset") { - } - if (path.getAttribute("interval") == "current") { - Entry& e(*_states[0]); - out << "<pre>" << e._name << ": "; - if (e.containsData()) { - e._data.print(out, true, " "); - } else { - out << "na"; - } - out << "\n</pre>"; - return; - } - const_cast<MemoryStatusViewer*>(this)->grabMemoryUsage(); - framework::SecondTime currentTime(_component.getClock().getTimeInSeconds()); - std::vector<const framework::MemoryAllocationType*> allocTypes( - _manager.getAllocationTypes()); - std::map<const framework::MemoryAllocationType*, uint32_t> colors( - assignColors(allocTypes)); - // Print memory usage graph - { - uint32_t mb = 1024 * 1024; - Graph memoryHistory("memhistory", Graph::SCHEME_CUSTOM); - std::vector<Graph::Point> total; - std::vector<Graph::Point> used; - std::vector<Graph::Point> usedWoCache; - uint32_t xval = 0; - for (std::deque<MemoryTimeEntry>::const_iterator it - = _memoryHistory.begin(); it != _memoryHistory.end(); - ++it, ++xval) - { - used.push_back(Graph::Point(xval, it->used)); - usedWoCache.push_back(Graph::Point(xval, it->usedWithoutCache)); - } - used.push_back(Graph::Point( - xval, _states[0]->_data.getUsedSize() / mb)); - usedWoCache.push_back(Graph::Point( - xval, _states[0]->_data.getUsedSizeIgnoringCache() / mb)); - uint32_t totalSize = _states[0]->_maxMemory / mb; - total.push_back(Graph::Point(0, totalSize)); - total.push_back(Graph::Point(xval, totalSize)); - memoryHistory.add(total, "Total memory", Graph::GREEN); - memoryHistory.add(used, "Used memory", Graph::YELLOW); - memoryHistory.add(usedWoCache, "Used memory excluding freeable cache", - Graph::RED); - out << "<p>Memory available for lowest priority (255): " - << _manager.getMemorySizeFreeForPriority(255) << " byte(s).</p>\n"; - out << "<h3>Historic memory usage</h3>\n"; - uint32_t yAxisUnit = ((totalSize / 4) / 256) * 256; - if (yAxisUnit == 0) yAxisUnit = (totalSize / 4); - if (yAxisUnit == 0) yAxisUnit = 1; - uint32_t size = yAxisUnit; - memoryHistory.addYAxisLabel(0, "0 B"); - while (size <= totalSize) { - std::ostringstream label; - if (size % 1024 == 0) { - label << (size / 1024) << " GB"; - } else { - label << size << " MB"; - } - memoryHistory.addYAxisLabel(size, label.str()); - size += yAxisUnit; - } - uint32_t xAxisUnit = ((_memoryHistory.size() / 4) / 24) * 24; - if (xAxisUnit == 0) xAxisUnit = _memoryHistoryPeriod.getTime(); - uint32_t startTime = ((currentTime.getTime() - / _memoryHistoryPeriod.getTime()) - / 24) * 24; - uint32_t stopTime = (currentTime.getTime() - / _memoryHistoryPeriod.getTime()) - - _memoryHistory.size() + 1; - memoryHistory.addXAxisLabel(xval, currentTime.toString()); - bool addedMiddlePoints = false; - while (startTime >= stopTime) { - if (currentTime.getTime() / _memoryHistoryPeriod.getTime() - - startTime > 48) - { - memoryHistory.addXAxisLabel( - (startTime - stopTime), - framework::SecondTime( - startTime * _memoryHistoryPeriod.getTime()) - .toString()); - addedMiddlePoints = true; - } - startTime -= xAxisUnit; - } - if (!addedMiddlePoints && _memoryHistory.size() > 2) { - memoryHistory.addXAxisLabel( - 1, - framework::SecondTime( - stopTime * _memoryHistoryPeriod.getTime()) - .toString()); - } - memoryHistory.setBorders(50, 0, 0, 30); - memoryHistory.setLegendPos(80, 20); - memoryHistory.printCanvas(out, 1000, 250); - memoryHistory.printScript(out, ""); - } - uint32_t maxUsedWithoutCache = 0; - for (uint32_t i=0; i<_states.size(); ++i) { - Entry& e(*_states[i]); - if (!e.containsData() - || e._data.getUsedSizeIgnoringCache() == maxUsedWithoutCache) - { - continue; - } - printSnapshot(out, e, colors); - maxUsedWithoutCache = e._data.getUsedSizeIgnoringCache(); - } - out << "<h3>Raw output of stored data</h3>\n" - << "<pre>\n"; - monitor.unlock(); - printDebugOutput(out); - out << "</pre>\n"; - out << "<h2>Memory used for metrics. (Not tracked in memory manager)</h2>\n" - << "<pre>\n" - << _metricManager.getMemoryConsumption(_metricManager.getMetricLock())->toString() - << "\n</pre>\n"; -} - -void -MemoryStatusViewer::run(framework::ThreadHandle& thread) -{ - while (!thread.interrupted()) { - vespalib::MonitorGuard monitor(_workerMonitor); - framework::SecondTime currentTime( - _component.getClock().getTimeInSeconds()); - if (_lastHistoryUpdate + _memoryHistoryPeriod <= currentTime - || _states[0]->_timeTaken + _memoryHistoryPeriod <= currentTime) - { - grabMemoryUsage(); - _processedTime = currentTime; - LOG(spam, "Done processing time %" PRIu64, currentTime.getTime()); - thread.registerTick(framework::PROCESS_CYCLE); - } else { - monitor.wait(thread.getWaitTime()); - thread.registerTick(framework::WAIT_CYCLE); - } - } -} - -// You should have worker monitor when calling this function -void -MemoryStatusViewer::grabMemoryUsage() -{ - framework::SecondTime currentTime(_component.getClock().getTimeInSeconds()); - MemoryState state(_component.getClock(), 0); - _manager.getState(state, true); - - if (_lastHistoryUpdate + _memoryHistoryPeriod <= currentTime) { - LOG(spam, "Adding another %" PRIu64 " sec entry to memory history.", - _memoryHistoryPeriod.getTime()); - // Add history once an hour - uint32_t mb = 1024 * 1024; - _memoryHistory.push_back(MemoryTimeEntry( - state.getMaxSnapshot().getUsedSize() / mb, - state.getMaxSnapshot().getUsedSizeIgnoringCache() / mb)); - if (_memoryHistory.size() > _memoryHistorySize) { - if (_memoryHistoryPeriod != framework::SecondTime(60 * 60)) { - uint32_t periodDiff = 60 * 60 / _memoryHistoryPeriod.getTime(); - std::deque<MemoryTimeEntry> newHistory; - uint32_t count = 0; - MemoryTimeEntry entry(0, 0); - for (std::deque<MemoryTimeEntry>::const_iterator it - = _memoryHistory.begin(); - it != _memoryHistory.end(); ++it) - { - entry.keepMax(*it); - if (++count == periodDiff) { - newHistory.push_back(entry); - entry = MemoryTimeEntry(0, 0); - count = 0; - } - } - if (entry.used != 0) { - newHistory.push_back(entry); - } - _memoryHistory.swap(newHistory); - _memoryHistoryPeriod = framework::SecondTime(60 * 60); - } - } - _lastHistoryUpdate += _memoryHistoryPeriod; - if (_lastHistoryUpdate + _allowedSlackPeriod < currentTime) { - LOGBP(warning, "Memory history is supposed to be tracked every %" - PRIu64 " seconds, but %" PRIu64" seconds have passed " - "since last update. Memory history graph will be " - "incorrect.", - _memoryHistoryPeriod.getTime(), - (currentTime - _lastHistoryUpdate + _memoryHistoryPeriod) - .getTime()); - _lastHistoryUpdate = currentTime; - } - } - LOG(spam, "Overwriting current with snapshot using %" PRIu64 " bytes.", - state.getCurrentSnapshot().getUsedSize()); - _states[0]->assign(state.getCurrentSnapshot(), - state.getTotalSize(), currentTime); - for (uint32_t i=1, n=_states.size(); i<n; ++i) { - if (currentTime - _states[i]->_timeTaken >= _states[i]->_maxAge - || state.getMaxSnapshot().getUsedSize() - > _states[i]->_data.getUsedSize()) - { - LOG(spam, "Updating period %s usage. Old usage was %" PRIu64 ". " - "Last set at %" PRIu64, - _states[i]->_name.c_str(), _states[i]->_data.getUsedSize(), - _states[i]->_timeTaken.getTime()); - _states[i]->assign(state.getMaxSnapshot(), - state.getTotalSize(), currentTime); - } - } -} - -void -MemoryStatusViewer::notifyThread() const -{ - vespalib::MonitorGuard monitor(_workerMonitor); - monitor.broadcast(); -} - -void -MemoryStatusViewer::printDebugOutput(std::ostream& out) const -{ - vespalib::MonitorGuard monitor(_workerMonitor); - for (uint32_t i=0; i<_states.size(); ++i) { - Entry& e(*_states[i]); - out << e._name << ": "; - if (e.containsData()) { - out << e._timeTaken.toString() << " Max memory " << e._maxMemory << " "; - e._data.print(out, true, " "); - } else { - out << "na"; - } - out << "\n\n"; - } -} - -} // storage diff --git a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h b/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h deleted file mode 100644 index 2c7510d371d..00000000000 --- a/storage/src/vespa/storage/frameworkimpl/memory/memorystatusviewer.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::MemoryStatusViewer - * - * \brief Generates status to access through status pages. - * - * Keeps a history of the largest memory inprints seen historically. This is - * done be defining periods, where a period is always a multiplum of the length - * of the period shorter than it. The last entry will store the biggest memory - * imprint ever seen, and the earlier entries will show biggest for their time - * period. - * - * To avoid having all periods cleared once the biggest period resets, the - * periods keep data for each of the periods one size below it. Thus, a year - * keeps data for 12 months, a month for 30 days, and so on. - * - * The memory state objects are divided in 3 parts. Current memory data, max - * memory data since since reset and counts for how often various events have - * happened. - * - * The counts will have their total count values stored in the current entry. - * When the next period is updated getting a copy of these counts, we can see - * how many counts have happened recently, by taking the current entry and - * subtract those accounted for earlier. - * - * The current memory data will not be interesting for anything than to show the - * actual now values in the current entry. - * - * The max since reset values will be the values used for the various periods. - * When a period is updated with new data for a subpart of their period, the - * max seen data is reset in the period in front, such that a lower maximum - * can be found. - */ - -#pragma once - -#include <vespa/storage/common/storagecomponent.h> -#include <vespa/storageframework/defaultimplementation/memory/memorystate.h> -#include <vespa/storageframework/generic/status/htmlstatusreporter.h> -#include <vespa/vespalib/util/document_runnable.h> -#include <vespa/vespalib/util/sync.h> -#include <deque> -#include <vector> - - -namespace metrics { - class MetricManager; -} - -namespace storage { - -class StorageServerInterface; - -class MemoryStatusViewer : public framework::HtmlStatusReporter, - private framework::Runnable -{ -public: - typedef framework::defaultimplementation::MemoryState::SnapShot SnapShot; - struct Entry { - typedef std::shared_ptr<Entry> SP; - - std::string _name; - framework::SecondTime _maxAge; - framework::SecondTime _timeTaken; - SnapShot _data; - uint64_t _maxMemory; - - Entry(const std::string& name, framework::Clock&, - framework::SecondTime maxAge); - bool containsData() const { return (_maxMemory != 0); } - - void assign(const SnapShot& snapshot, uint64_t maxMemory, - framework::SecondTime time) - { - _data = snapshot; - _maxMemory = maxMemory; - _timeTaken = time; - } - }; - - struct MemoryTimeEntry { - uint64_t used; - uint64_t usedWithoutCache; - - MemoryTimeEntry(uint64_t u, uint64_t wo) - : used(u), usedWithoutCache(wo) {} - - void keepMax(const MemoryTimeEntry& e) { - used = (used > e.used ? used : e.used); - usedWithoutCache = (usedWithoutCache > e.usedWithoutCache - ? usedWithoutCache : e.usedWithoutCache); - } - }; - -private: - framework::Component _component; - framework::defaultimplementation::MemoryManager& _manager; - const metrics::MetricManager& _metricManager; - vespalib::Monitor _workerMonitor; - - std::vector<Entry::SP> _states; - std::deque<MemoryTimeEntry> _memoryHistory; - uint32_t _memoryHistorySize; - framework::SecondTime _memoryHistoryPeriod; - framework::SecondTime _allowedSlackPeriod; - framework::SecondTime _lastHistoryUpdate; - framework::Thread::UP _thread; - framework::SecondTime _processedTime; - - void addEntry(const std::string& name, uint32_t maxAge) { - _states.push_back(Entry::SP(new Entry(name, _component.getClock(), - framework::SecondTime(maxAge)))); - } - void run(framework::ThreadHandle&) override; - void grabMemoryUsage(); - void printSnapshot(std::ostream& out, Entry& entry, - std::map<const framework::MemoryAllocationType*, - uint32_t>& colors) const; - -public: - MemoryStatusViewer( - framework::defaultimplementation::MemoryManager&, - const metrics::MetricManager&, - StorageComponentRegister&); - ~MemoryStatusViewer(); - - void reportHtmlHeaderAdditions(std::ostream&, const framework::HttpUrlPath&) const override; - void reportHtmlStatus(std::ostream&, const framework::HttpUrlPath&) const override; - - /** Useful for testing. */ - framework::SecondTime getProcessedTime() const { return _processedTime; } - void notifyThread() const; - void printDebugOutput(std::ostream&) const; - -}; - -} diff --git a/storage/src/vespa/storage/persistence/fieldvisitor.h b/storage/src/vespa/storage/persistence/fieldvisitor.h index 96b73ca779b..4b47c68e33b 100644 --- a/storage/src/vespa/storage/persistence/fieldvisitor.h +++ b/storage/src/vespa/storage/persistence/fieldvisitor.h @@ -42,7 +42,6 @@ public: void visitArithmeticValueNode(const document::select::ArithmeticValueNode &) override {} void visitFunctionValueNode(const document::select::FunctionValueNode &) override {} void visitIdValueNode(const document::select::IdValueNode &) override {} - void visitSearchColumnValueNode(const document::select::SearchColumnValueNode &) override {} void visitFloatValueNode(const document::select::FloatValueNode &) override {} void visitVariableValueNode(const document::select::VariableValueNode &) override {} void visitIntegerValueNode(const document::select::IntegerValueNode &) override {} diff --git a/storage/src/vespa/storage/persistence/messages.cpp b/storage/src/vespa/storage/persistence/messages.cpp index 876067e4e15..720371a68f1 100644 --- a/storage/src/vespa/storage/persistence/messages.cpp +++ b/storage/src/vespa/storage/persistence/messages.cpp @@ -6,17 +6,14 @@ using document::BucketSpace; namespace storage { -GetIterCommand::GetIterCommand(framework::MemoryToken::UP token, - const document::Bucket &bucket, +GetIterCommand::GetIterCommand(const document::Bucket &bucket, const spi::IteratorId iteratorId, uint32_t maxByteSize) : api::InternalCommand(ID), - _token(std::move(token)), _bucket(bucket), _iteratorId(iteratorId), _maxByteSize(maxByteSize) { - assert(_token.get()); } GetIterCommand::~GetIterCommand() { } @@ -38,7 +35,6 @@ GetIterCommand::makeReply() { GetIterReply::GetIterReply(GetIterCommand& cmd) : api::InternalReply(ID, cmd), - _token(cmd.releaseMemoryToken()), _bucket(cmd.getBucket()), _completed(false) { } diff --git a/storage/src/vespa/storage/persistence/messages.h b/storage/src/vespa/storage/persistence/messages.h index 7bbeea8a12a..ba7f5979569 100644 --- a/storage/src/vespa/storage/persistence/messages.h +++ b/storage/src/vespa/storage/persistence/messages.h @@ -1,7 +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 <vespa/storageframework/generic/memory/memorytoken.h> #include <vespa/storageapi/message/internal.h> #include <vespa/persistence/spi/docentry.h> #include <vespa/persistence/spi/bucket.h> @@ -14,7 +13,6 @@ namespace storage { class GetIterCommand : public api::InternalCommand { private: - mutable framework::MemoryToken::UP _token; document::Bucket _bucket; spi::IteratorId _iteratorId; uint32_t _maxByteSize; @@ -24,8 +22,7 @@ public: typedef std::unique_ptr<GetIterCommand> UP; typedef std::shared_ptr<GetIterCommand> SP; - GetIterCommand(framework::MemoryToken::UP token, - const document::Bucket &bucket, + GetIterCommand(const document::Bucket &bucket, const spi::IteratorId iteratorId, uint32_t maxByteSize); ~GetIterCommand(); @@ -44,13 +41,11 @@ public: void print(std::ostream& out, bool verbose, const std::string& indent) const override; private: - framework::MemoryToken::UP releaseMemoryToken() { return std::move(_token); } friend class GetIterReply; }; class GetIterReply : public api::InternalReply { private: - framework::MemoryToken::UP _token; document::Bucket _bucket; std::vector<spi::DocEntry::UP> _entries; bool _completed; diff --git a/storage/src/vespa/storage/storageserver/CMakeLists.txt b/storage/src/vespa/storage/storageserver/CMakeLists.txt index 3d0a0fdfa14..c0238922a91 100644 --- a/storage/src/vespa/storage/storageserver/CMakeLists.txt +++ b/storage/src/vespa/storage/storageserver/CMakeLists.txt @@ -11,7 +11,6 @@ vespa_add_library(storage_storageserver documentapiconverter.cpp fnetlistener.cpp mergethrottler.cpp - messageallocationtypes.cpp messagesink.cpp opslogger.cpp priorityconverter.cpp diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.cpp b/storage/src/vespa/storage/storageserver/communicationmanager.cpp index eae51b90165..c19dc7cfd27 100644 --- a/storage/src/vespa/storage/storageserver/communicationmanager.cpp +++ b/storage/src/vespa/storage/storageserver/communicationmanager.cpp @@ -96,13 +96,6 @@ StorageTransportContext::StorageTransportContext(std::unique_ptr<RPCRequestWrapp StorageTransportContext::~StorageTransportContext() { } -const framework::MemoryAllocationType& -CommunicationManager::getAllocationType(api::StorageMessage& msg) const -{ - return _messageAllocTypes.getType(msg.getType().getId()); -} - - void CommunicationManager::receiveStorageReply(const std::shared_ptr<api::StorageReply>& reply) { @@ -296,8 +289,7 @@ CommunicationManager::CommunicationManager(StorageComponentRegister& compReg, co _configUri(configUri), _closed(false), _bucketResolver(std::make_unique<PlaceHolderBucketResolver>()), - _docApiConverter(configUri, *_bucketResolver), - _messageAllocTypes(_component.getMemoryManager()) + _docApiConverter(configUri, *_bucketResolver) { _component.registerMetricUpdateHook(*this, framework::SecondTime(5)); _component.registerMetric(_metrics); @@ -483,33 +475,9 @@ CommunicationManager::process(const std::shared_ptr<api::StorageMessage>& msg) void CommunicationManager::enqueue(const std::shared_ptr<api::StorageMessage> & msg) { - using MemoryToken = framework::MemoryToken; assert(msg.get()); - - const uint32_t memoryFootprint = msg->getMemoryFootprint(); - MemoryToken::UP token = _component.getMemoryManager().allocate(getAllocationType(*msg), memoryFootprint * 2, - memoryFootprint * 2, msg->getPriority()); - - if (token) { - msg->setMemoryToken(std::move(token)); - - LOG(spam, "Enq storage message %s, priority %d", msg->toString().c_str(), msg->getPriority()); - _eventQueue.enqueue(msg); - } else { - _metrics.failedDueToTooLittleMemory.inc(); - std::ostringstream ost; - ost << "Failed to aquire " << (memoryFootprint * 2) - << " bytes of memory to handle command of type " - << msg->getType() << "\n"; - LOG(spam, "%s", ost.str().c_str()); - api::StorageCommand* cmd(dynamic_cast<api::StorageCommand*>(msg.get())); - - if (cmd) { - std::shared_ptr<api::StorageReply> reply(cmd->makeReply()); - reply->setResult(api::ReturnCode(api::ReturnCode::BUSY, ost.str())); - sendReply(reply); - } - } + LOG(spam, "Enq storage message %s, priority %d", msg->toString().c_str(), msg->getPriority()); + _eventQueue.enqueue(msg); } bool @@ -770,10 +738,11 @@ CommunicationManager::run(framework::ThreadHandle& thread) if (_eventQueue.getNext(msg, 100)) { process(msg); } - for (Protocols::iterator it(_earlierGenerations.begin()); + std::lock_guard<std::mutex> guard(_earlierGenerationsLock); + for (EarlierProtocols::iterator it(_earlierGenerations.begin()); !_earlierGenerations.empty() && ((it->first + TEN_MINUTES) < _component.getClock().getTimeInSeconds()); - _earlierGenerations.begin()) + it = _earlierGenerations.begin()) { _earlierGenerations.erase(it); } @@ -798,8 +767,8 @@ void CommunicationManager::updateMessagebusProtocol( if (_mbus.get()) { framework::SecondTime now(_component.getClock().getTimeInSeconds()); mbus::IProtocol::SP newDocumentProtocol(new documentapi::DocumentProtocol( *_component.getLoadTypes(), repo)); + std::lock_guard<std::mutex> guard(_earlierGenerationsLock); _earlierGenerations.push_back(std::make_pair(now, _mbus->getMessageBus().putProtocol(newDocumentProtocol))); - mbus::IProtocol::SP newStorageProtocol(new mbusprot::StorageProtocol(repo, *_component.getLoadTypes())); _earlierGenerations.push_back(std::make_pair(now, _mbus->getMessageBus().putProtocol(newStorageProtocol))); } diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.h b/storage/src/vespa/storage/storageserver/communicationmanager.h index 4cf3f33e6ea..f4f4aa5a236 100644 --- a/storage/src/vespa/storage/storageserver/communicationmanager.h +++ b/storage/src/vespa/storage/storageserver/communicationmanager.h @@ -11,7 +11,6 @@ #pragma once #include "communicationmanagermetrics.h" -#include "messageallocationtypes.h" #include "documentapiconverter.h" #include <vespa/storage/common/storagelink.h> #include <vespa/storage/common/storagecomponent.h> @@ -27,6 +26,7 @@ #include <map> #include <queue> #include <atomic> +#include <mutex> namespace mbus { class RPCMessageBus; @@ -139,8 +139,10 @@ private: PriorityQueue _eventQueue; // XXX: Should perhaps use a configsubscriber and poll from StorageComponent ? std::unique_ptr<config::ConfigFetcher> _configFetcher; - typedef std::vector< std::pair<framework::SecondTime, mbus::IProtocol::SP> > Protocols; - Protocols _earlierGenerations; + using EarlierProtocol = std::pair<framework::SecondTime, mbus::IProtocol::SP>; + using EarlierProtocols = std::vector<EarlierProtocol>; + std::mutex _earlierGenerationsLock; + EarlierProtocols _earlierGenerations; void onOpen() override; void onClose() override; @@ -171,10 +173,7 @@ private: std::unique_ptr<BucketResolver> _bucketResolver; DocumentApiConverter _docApiConverter; framework::Thread::UP _thread; - MessageAllocationTypes _messageAllocTypes; - const framework::MemoryAllocationType& - getAllocationType(api::StorageMessage& msg) const; void updateMetrics(const MetricLockGuard &) override; // Test needs access to configure() for live reconfig testing. diff --git a/storage/src/vespa/storage/storageserver/framework.h b/storage/src/vespa/storage/storageserver/framework.h index 724b4fe0944..f0ea1d71aa6 100644 --- a/storage/src/vespa/storage/storageserver/framework.h +++ b/storage/src/vespa/storage/storageserver/framework.h @@ -18,7 +18,6 @@ #include <vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.h> #include <vespa/storageframework/defaultimplementation/clock/realclock.h> -#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h> #include <vespa/storageframework/defaultimplementation/thread/threadpoolimpl.h> namespace storage { @@ -27,7 +26,6 @@ struct Framework { // Typedefs to simplify the remainder of the interface typedef StorageComponentRegisterImpl CompReg; typedef framework::defaultimplementation::RealClock RealClock; - typedef framework::defaultimplementation::MemoryManager MemoryManager; /** * You can provide your own clock implementation. Useful in testing where @@ -48,20 +46,10 @@ struct Framework { */ FastOS_ThreadPool& getThreadPool() { return _threadPool.getThreadPool(); } - /** - * Get the memory manager. Components that wants to print status of memory - * manager need access to the actual implementation. - */ - MemoryManager& getMemoryManager() { return _memoryManager; } - - void setMaximumMemoryUsage(uint64_t max); - private: CompReg _componentRegister; framework::Clock::UP _clock; framework::defaultimplementation::ThreadPoolImpl _threadPool; - framework::defaultimplementation::AllocationLogic* _memoryLogic; - MemoryManager _memoryManager; }; diff --git a/storage/src/vespa/storage/storageserver/messageallocationtypes.cpp b/storage/src/vespa/storage/storageserver/messageallocationtypes.cpp deleted file mode 100644 index ac2d99a439c..00000000000 --- a/storage/src/vespa/storage/storageserver/messageallocationtypes.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "messageallocationtypes.h" -#include <vespa/storageapi/messageapi/storagemessage.h> -#include <vespa/vespalib/util/exceptions.h> -#include <vespa/vespalib/stllike/asciistream.h> - -namespace storage { - -MessageAllocationTypes::MessageAllocationTypes(framework::MemoryManagerInterface& manager) -{ - using api::MessageType; - using framework::MemoryAllocationType; - - _types.resize(MessageType::MESSAGETYPE_MAX_ID); - _types[MessageType::DOCBLOCK_ID] = &manager.registerAllocationType(MemoryAllocationType("MESSAGE_DOCBLOCK")); - _types[MessageType::DOCBLOCK_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MESSAGE_DOCBLOCK_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GET", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::GET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::INTERNAL_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::INTERNAL")); - _types[MessageType::INTERNAL_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::INTERNAL_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::PUT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::PUT", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::PUT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::PUT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::REMOVE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVE", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::REMOVE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::REVERT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REVERT")); - _types[MessageType::REVERT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REVERT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::VISITOR_CREATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_CREATE", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::VISITOR_CREATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_CREATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::VISITOR_DESTROY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_DESTROY")); - _types[MessageType::VISITOR_DESTROY_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_DESTROY_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::REQUESTBUCKETINFO_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REQUESTBUCKETINFO")); - _types[MessageType::REQUESTBUCKETINFO_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REQUESTBUCKETINFO_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::NOTIFYBUCKETCHANGE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::NOTIFYBUCKETCHANGE")); - _types[MessageType::NOTIFYBUCKETCHANGE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::NOTIFYBUCKETCHANGE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::CREATEBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::CREATEBUCKET")); - _types[MessageType::CREATEBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::CREATEBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::MERGEBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MERGEBUCKET")); - _types[MessageType::MERGEBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MERGEBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::DELETEBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DELETEBUCKET")); - _types[MessageType::DELETEBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DELETEBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SETNODESTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETNODESTATE", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SETNODESTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETNODESTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETNODESTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETNODESTATE", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETNODESTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETNODESTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SETSYSTEMSTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETSYSTEMSTATE", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SETSYSTEMSTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETSYSTEMSTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETSYSTEMSTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETSYSTEMSTATE", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETSYSTEMSTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETSYSTEMSTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETBUCKETDIFF_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETDIFF", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETBUCKETDIFF_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETDIFF_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::APPLYBUCKETDIFF_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::APPLYBUCKETDIFF", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::APPLYBUCKETDIFF_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::APPLYBUCKETDIFF_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::VISITOR_INFO_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_INFO")); - _types[MessageType::VISITOR_INFO_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::VISITOR_INFO_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SEARCHRESULT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SEARCHRESULT")); - _types[MessageType::SEARCHRESULT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SEARCHRESULT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SPLITBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SPLITBUCKET")); - _types[MessageType::SPLITBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SPLITBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::JOINBUCKETS_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::JOINBUCKETS")); - _types[MessageType::JOINBUCKETS_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::JOINBUCKETS_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::SETBUCKETSTATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETBUCKETSTATE")); - _types[MessageType::SETBUCKETSTATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::SETBUCKETSTATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::MULTIOPERATION_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MULTIOPERATION", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::MULTIOPERATION_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MULTIOPERATION_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::DOCUMENTSUMMARY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTSUMMARY")); - _types[MessageType::DOCUMENTSUMMARY_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTSUMMARY_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::MAPVISITOR_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MAPVISITOR")); - _types[MessageType::MAPVISITOR_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::MAPVISITOR_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::STATBUCKET_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::STATBUCKET", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::STATBUCKET_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::STATBUCKET_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::GETBUCKETLIST_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETLIST", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::GETBUCKETLIST_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::GETBUCKETLIST_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::DOCUMENTLIST_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTLIST")); - _types[MessageType::DOCUMENTLIST_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::DOCUMENTLIST_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::UPDATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::UPDATE", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::UPDATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::UPDATE_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::EMPTYBUCKETS_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::EMPTYBUCKETS")); - _types[MessageType::EMPTYBUCKETS_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::EMPTYBUCKETS_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::REMOVELOCATION_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVELOCATION", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::REMOVELOCATION_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::REMOVELOCATION_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::QUERYRESULT_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::QUERYRESULT")); - _types[MessageType::QUERYRESULT_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::QUERYRESULT_REPLY", framework::MemoryAllocationType::FORCE_ALLOCATE)); - _types[MessageType::BATCHPUTREMOVE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHPUTREMOVE", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::BATCHPUTREMOVE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHPUTREMOVE_REPLY", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::BATCHDOCUMENTUPDATE_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHDOCUMENTUPDATE", framework::MemoryAllocationType::EXTERNAL_LOAD)); - _types[MessageType::BATCHDOCUMENTUPDATE_REPLY_ID] = &manager.registerAllocationType(MemoryAllocationType("MessageType::BATCHDOCUMENTUPDATE_REPLY", framework::MemoryAllocationType::EXTERNAL_LOAD)); -} - -const framework::MemoryAllocationType& -MessageAllocationTypes::getType(uint32_t type) const { - if (_types.size() > size_t(type) && _types[type] != 0) { - return *_types[type]; - } - vespalib::asciistream ost; - ost << "No type registered with value " << type << "."; - throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC); -} - -} // storage diff --git a/storage/src/vespa/storage/storageserver/messageallocationtypes.h b/storage/src/vespa/storage/storageserver/messageallocationtypes.h deleted file mode 100644 index 8f7e8e4b3c3..00000000000 --- a/storage/src/vespa/storage/storageserver/messageallocationtypes.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::MessageAllocationTypes - * - * \brief Memory allocation types for messages in storage. - */ -#pragma once - -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> -#include <vector> - -namespace storage { - -class MessageAllocationTypes { - std::vector<const framework::MemoryAllocationType*> _types; - -public: - MessageAllocationTypes(framework::MemoryManagerInterface& manager); - - const framework::MemoryAllocationType& getType(uint32_t type) const; -}; - -} - diff --git a/storage/src/vespa/storage/storageserver/storagenode.cpp b/storage/src/vespa/storage/storageserver/storagenode.cpp index 855efaed6aa..ba1556bd3b9 100644 --- a/storage/src/vespa/storage/storageserver/storagenode.cpp +++ b/storage/src/vespa/storage/storageserver/storagenode.cpp @@ -7,7 +7,6 @@ #include "storagemetricsset.h" #include "storagenodecontext.h" -#include <vespa/storage/frameworkimpl/memory/memorystatusviewer.h> #include <vespa/storage/frameworkimpl/status/statuswebserver.h> #include <vespa/storage/frameworkimpl/thread/deadlockdetector.h> #include <vespa/storage/common/statusmetricconsumer.h> @@ -117,7 +116,6 @@ StorageNode::initialize() // and store them away, while having the config lock. subscribeToConfigs(); - _context.getMemoryManager().setMaximumMemoryUsage(_serverConfig->memorytouse); updateUpgradeFlag(*_clusterConfig); // First update some basics that doesn't depend on anything else to be @@ -157,11 +155,6 @@ StorageNode::initialize() initializeNodeSpecific(); - _memoryStatusViewer.reset(new MemoryStatusViewer( - _context.getMemoryManager(), - _context.getComponentRegister().getMetricManager(), - _context.getComponentRegister())); - _statusMetrics.reset(new StatusMetricConsumer( _context.getComponentRegister(), _context.getComponentRegister().getMetricManager())); _stateReporter.reset(new StateReporter( @@ -259,14 +252,6 @@ StorageNode::handleLiveConfigUpdate(const InitialGuard & initGuard) DIFFERWARN(clusterName, "Cannot alter cluster name of node live"); DIFFERWARN(nodeIndex, "Cannot alter node index of node live"); DIFFERWARN(isDistributor, "Cannot alter role of node live"); - { - if (DIFFER(memorytouse)) { - LOG(info, "Live config update: Memory to use changed from %" PRId64 " to %" PRId64 ".", - oldC.memorytouse, newC.memorytouse); - ASSIGN(memorytouse); - _context.getMemoryManager().setMaximumMemoryUsage(newC.memorytouse); - } - } _serverConfig.reset(new StorServerConfig(oldC)); _newServerConfig.reset(); (void)updated; @@ -413,10 +398,6 @@ StorageNode::shutdown() LOG(debug, "Deleting state reporter"); _stateReporter.reset(); } - if (_memoryStatusViewer) { - LOG(debug, "Deleting memory status viewer"); - _memoryStatusViewer.reset(); - } if (_stateManager) { LOG(debug, "Deleting state manager"); _stateManager.reset(); diff --git a/storage/src/vespa/storage/storageserver/storagenode.h b/storage/src/vespa/storage/storageserver/storagenode.h index 9b727ef3e0c..e9d3004be68 100644 --- a/storage/src/vespa/storage/storageserver/storagenode.h +++ b/storage/src/vespa/storage/storageserver/storagenode.h @@ -121,8 +121,6 @@ private: // Depends on bucket databases and stop() functionality std::unique_ptr<DeadLockDetector> _deadLockDetector; - // Depends on dead lock detector and threadpool - std::unique_ptr<MemoryStatusViewer> _memoryStatusViewer; // Depends on metric manager std::unique_ptr<StatusMetricConsumer> _statusMetrics; // Depends on metric manager diff --git a/storage/src/vespa/storage/storageserver/storagenodecontext.cpp b/storage/src/vespa/storage/storageserver/storagenodecontext.cpp index 2e1aa52e68d..75e1f12773f 100644 --- a/storage/src/vespa/storage/storageserver/storagenodecontext.cpp +++ b/storage/src/vespa/storage/storageserver/storagenodecontext.cpp @@ -2,31 +2,15 @@ #include "storagenodecontext.h" -#include <vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h> - namespace storage { -using framework::defaultimplementation::AllocationLogic; -using framework::defaultimplementation::PriorityMemoryLogic; - StorageNodeContext::StorageNodeContext(ComponentRegister::UP compReg, framework::Clock::UP clock) : _componentRegister(std::move(compReg)), _clock(std::move(clock)), - _threadPool(*_clock), - _memoryLogic(new PriorityMemoryLogic(*_clock, 1024 * 1024 * 1024)), - _memoryManager(AllocationLogic::UP(_memoryLogic)) + _threadPool(*_clock) { _componentRegister->setClock(*_clock); _componentRegister->setThreadPool(_threadPool); - _componentRegister->setMemoryManager(_memoryManager); -} - -void -StorageNodeContext::setMaximumMemoryUsage(uint64_t max) -{ - using storage::framework::defaultimplementation::PriorityMemoryLogic; - dynamic_cast<PriorityMemoryLogic*>(_memoryLogic) - ->setMaximumMemoryUsage(max); } } // storage diff --git a/storage/src/vespa/storage/storageserver/storagenodecontext.h b/storage/src/vespa/storage/storageserver/storagenodecontext.h index 0149f975f63..eabca618bfb 100644 --- a/storage/src/vespa/storage/storageserver/storagenodecontext.h +++ b/storage/src/vespa/storage/storageserver/storagenodecontext.h @@ -18,7 +18,6 @@ #include <vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.h> #include <vespa/storageframework/defaultimplementation/clock/realclock.h> -#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h> #include <vespa/storageframework/defaultimplementation/thread/threadpoolimpl.h> namespace storage { @@ -27,7 +26,6 @@ struct StorageNodeContext { // Typedefs to simplify the remainder of the interface typedef StorageComponentRegisterImpl ComponentRegister; typedef framework::defaultimplementation::RealClock RealClock; - typedef framework::defaultimplementation::MemoryManager MemoryManager; /** * Get the actual component register. Available as the actual type as the @@ -42,14 +40,6 @@ struct StorageNodeContext { */ FastOS_ThreadPool& getThreadPool() { return _threadPool.getThreadPool(); } - /** - * Get the memory manager. Components that wants to print status of memory - * manager need access to the actual implementation. - */ - MemoryManager& getMemoryManager() { return _memoryManager; } - - void setMaximumMemoryUsage(uint64_t max); - protected: // Initialization has been split in two as subclass needs to initialize // component register before sending it on. @@ -59,8 +49,6 @@ private: ComponentRegister::UP _componentRegister; framework::Clock::UP _clock; framework::defaultimplementation::ThreadPoolImpl _threadPool; - framework::defaultimplementation::AllocationLogic* _memoryLogic; - MemoryManager _memoryManager; }; diff --git a/storage/src/vespa/storage/storageutil/CMakeLists.txt b/storage/src/vespa/storage/storageutil/CMakeLists.txt index 6317465ab8e..df942a049fb 100644 --- a/storage/src/vespa/storage/storageutil/CMakeLists.txt +++ b/storage/src/vespa/storage/storageutil/CMakeLists.txt @@ -2,9 +2,6 @@ vespa_add_library(storage_storageutil OBJECT SOURCES bloomfilter.cpp - graph.cpp - palette.cpp - piechart.cpp DEPENDS AFTER storage_storageconfig diff --git a/storage/src/vespa/storage/storageutil/functor.h b/storage/src/vespa/storage/storageutil/functor.h deleted file mode 100644 index 4ca6b4bf926..00000000000 --- a/storage/src/vespa/storage/storageutil/functor.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @ingroup storageutil - * - * @brief Functors ards storage, not dependent on external messaging. - * - * @author H�kon Humberset - * @date 2005-05-13 - * @version $Id$ - */ - -#pragma once - -namespace storage { - -class Functor { -public: - - /** - * For instance, using this functor you can say: - * - * string mystring("this is a test"); - * for_each(mystring.begin(), mystring.end(), - * Functor.Replace<char>(' ', '_')); - * - * or - * - * vector<string> myvector; - * for_each(myvector.begin(), myvector.end(), - * Functor.Replace<string>("this", "that")); - */ - template<class T> - class Replace { - private: - const T& _what; - const T& _with; - - public: - Replace(const T& what, const T& with) - : _what(what), - _with(with) {} - - void operator()(T& element) const - { if (element == _what) element = _with; } - }; - - /** - * To easily delete containers of pointers. - * - * for_each(myvec.begin(), myvec.end(), Functor::DeletePointer()); - */ - class DeletePointer { - public: - template<class T> void operator()(T *ptr) const { delete ptr; } - }; - -}; - -} - diff --git a/storage/src/vespa/storage/storageutil/graph.cpp b/storage/src/vespa/storage/storageutil/graph.cpp deleted file mode 100644 index 391d5c14c66..00000000000 --- a/storage/src/vespa/storage/storageutil/graph.cpp +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "graph.h" -#include <vespa/vespalib/util/exceptions.h> -#include <iomanip> - -namespace storage { - -Graph::Entry::Entry(const std::vector<Point>& v, const std::string& name, int32_t col) - : points(v), - _name(name), - _color(col) -{} - - Graph::Entry::~Entry() {} - -void -Graph::printHtmlHeadAdditions(std::ostream& out, const std::string& indent) -{ - (void) out; - (void) indent; - // FIXME this used to reference Yahoo-internal JS URIs -} - -Graph::Graph(const std::string& name, ColorScheme cs) - : _name(name), - _graphs(), - _colors(cs), - _leftPad(50), - _rightPad(0), - _topPad(0), - _bottomPad(0) -{} - -Graph::~Graph() {} - -void -Graph::add(const std::vector<Point>& values, const std::string& name) -{ - if (_colors == SCHEME_CUSTOM) { - throw vespalib::IllegalArgumentException( - "Using custom color scheme you need to supply a color for each " - "graph.", VESPA_STRLOC); - } - _graphs.push_back(Entry(values, name, UNDEFINED)); -} - -void -Graph::add(const std::vector<Point>& values, const std::string& name, Color c) -{ - if (_colors != SCHEME_CUSTOM) { - throw vespalib::IllegalArgumentException( - "Not using custom color scheme you cannot supply a custom " - "color for a graph.", VESPA_STRLOC); - } - _graphs.push_back(Entry(values, name, c)); -} - -void -Graph::add(const std::vector<Point>& values, const std::string& name, int32_t c) -{ - if (_colors != SCHEME_CUSTOM) { - throw vespalib::IllegalArgumentException( - "Not using custom color scheme you cannot supply a custom " - "color for a graph.", VESPA_STRLOC); - } - _graphs.push_back(Entry(values, name, (Color) c)); -} - -void -Graph::printCanvas(std::ostream& out, uint32_t width, uint32_t height) const -{ - out << "<div><canvas id=\"" << _name << "\" width=\"" << width - << "\" height=\"" << height << "\"/></div>"; -} - -namespace { - void printDatasetDefinition(std::ostream& o, const std::string& i, - const std::string& n, const std::vector<Graph::Entry>& e) - { - o << i << " var " << n << "_dataset = {\n" << std::dec; - bool first = true; - for (std::vector<Graph::Entry>::const_iterator it = e.begin(); - it != e.end(); ++it) - { - if (!first) o << ",\n"; - first = false; - o << i << " '" << it->_name << "': ["; - for (uint32_t j=0; j<it->points.size(); ++j) { - if (j != 0) o << ", "; - o << "[" << it->points[j].x << ", " << it->points[j].y << "]"; - } - o << "]"; - } - o << "\n" << i << " };"; - } - - void printCustomColorScheme(std::ostream& o, const std::string& i, - const std::string& n, const std::vector<Graph::Entry>& e) - { - o << " var " << n << "_customScheme = new Hash({\n" << std::hex; - bool first = true; - for (std::vector<Graph::Entry>::const_iterator it = e.begin(); - it != e.end(); ++it) - { - if (!first) o << ",\n"; - first = false; - o << i << " '" << it->_name << "': '#" << std::setw(6) - << std::setfill('0') << (it->_color & 0x00FFFFFF) << "'"; - } - o << "\n" << i << " });" << std::dec; - } - - void printOptions(std::ostream& o, const std::string& i, - const std::string& n, Graph::ColorScheme c, - const std::vector<Graph::Axis>& xAxis, - const std::vector<Graph::Axis>& yAxis, - uint32_t leftpad, uint32_t rightpad, - uint32_t toppad, uint32_t bottompad, - uint32_t legendXPos, uint32_t legendYPos) - { - o << " var " << n << "_options = {\n" - << i << " padding: {\n" - << i << " left: " << leftpad << ",\n" - << i << " right: " << rightpad << ",\n" - << i << " top: " << toppad << ",\n" - << i << " bottom: " << bottompad << ",\n" - << i << " },\n" - << i << " background: {\n" - << i << " color: '#ffffff'\n" - << i << " },\n" - << i << " shouldFill: true,\n"; - if (c == Graph::SCHEME_CUSTOM) { - o << i << " \"colorScheme\": " << n << "_customScheme,\n"; - } else { - o << i << " colorScheme: '"; - switch (c) { - case Graph::SCHEME_RED: o << "red"; break; - case Graph::SCHEME_BLUE: o << "blue"; break; - case Graph::SCHEME_CUSTOM: break; - } - o << "',\n"; - } - o << i << " legend: {\n" - << i << " opacity: 0.9,\n" - << i << " position: {\n" - << i << " top: " << legendYPos << ",\n" - << i << " left: " << legendXPos << "\n" - << i << " }\n" - << i << " },\n" - << i << " axis: {\n" - << i << " labelColor: '#000000',\n" - << i << " x: {\n"; - if (xAxis.size() > 0) { - o << i << " ticks: [\n"; - for (uint32_t j=0; j<xAxis.size(); ++j) { - o << i << " {v:" << xAxis[j].value << ", label:'" - << xAxis[j].name << "'},\n"; - } - o << i << " ]\n"; - } - o << i << " },\n" - << i << " y: {\n"; - if (yAxis.size() > 0) { - o << i << " ticks: [\n"; - for (uint32_t j=0; j<yAxis.size(); ++j) { - o << i << " {v:" << yAxis[j].value << ", label:'" - << yAxis[j].name << "'},\n"; - } - o << i << " ]\n"; - } - - o << i << " }\n" - << i << " }\n" - << i << " };"; - } - - void printChart(std::ostream& o, const std::string& i, const std::string& n) - { - o << " var " << n << "_chart = new Plotr.LineChart('" << n - << "', " << n << "_options);\n" - << i << " " << n << "_chart.addDataset(" << n << "_dataset);\n" - << i << " " << n << "_chart.render();"; - } -} - -void -Graph::printScript(std::ostream& out, const std::string& indent) const -{ - out << "<script type=\"text/javascript\">\n"; - printDatasetDefinition(out, indent, _name, _graphs); - if (_colors == SCHEME_CUSTOM) { - out << "\n" << indent; - printCustomColorScheme(out, indent, _name, _graphs); - } - out << "\n" << indent; - printOptions(out, indent, _name, _colors, _xAxis, _yAxis, - _leftPad, _rightPad, _topPad, _bottomPad, - _legendXPos, _legendYPos); - out << "\n" << indent; - printChart(out, indent, _name); - out << "\n" << indent << "</script>"; -} - -} // storage - diff --git a/storage/src/vespa/storage/storageutil/graph.h b/storage/src/vespa/storage/storageutil/graph.h deleted file mode 100644 index e39a0b59686..00000000000 --- a/storage/src/vespa/storage/storageutil/graph.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::Graph - * \ingroup util - * - * \brief Helper library to print graphs in HTML. - */ - -#pragma once - -#include <string> -#include <vector> -#include <ostream> - -namespace storage { - -class Graph { -public: - enum ColorScheme { - SCHEME_CUSTOM, - SCHEME_RED, - SCHEME_BLUE - }; - enum Color { - UNDEFINED = -1, - BLACK = 0x000000, - RED = 0xFF0000, - GREEN = 0x00FF00, - BLUE = 0x0000FF, - WHITE = 0xFFFFFF, - YELLOW = 0xFFFF00 - }; - struct Point { - double x; - double y; - - Point(double x_, double y_) : x(x_), y(y_) {} - }; - struct Entry { - std::vector<Point> points; - std::string _name; - int32_t _color; - - Entry(const std::vector<Point>& v, const std::string& name, int32_t col); - Entry(Entry &&) = default; - Entry & operator = (Entry &&) = default; - ~Entry(); - }; - struct Axis { - double value; - std::string name; - - Axis(double val, const std::string& name_) : value(val), name(name_) {} - }; - - static void printHtmlHeadAdditions( - std::ostream& out, const std::string& indent = ""); - -private: - const std::string _name; - std::vector<Entry> _graphs; - ColorScheme _colors; - std::vector<Axis> _xAxis; - std::vector<Axis> _yAxis; - uint32_t _leftPad; - uint32_t _rightPad; - uint32_t _topPad; - uint32_t _bottomPad; - uint32_t _legendXPos; - uint32_t _legendYPos; - -public: - Graph(const std::string&, ColorScheme = SCHEME_BLUE); - ~Graph(); - - void add(const std::vector<Point>&, const std::string& name); - void add(const std::vector<Point>&, const std::string& name, Color c); - void add(const std::vector<Point>&, const std::string& name, int32_t color); - - void addXAxisLabel(double value, const std::string& name) - { _xAxis.push_back(Axis(value, name)); } - void addYAxisLabel(double value, const std::string& name) - { _yAxis.push_back(Axis(value, name)); } - - void setBorders(uint32_t left, uint32_t right, - uint32_t top, uint32_t bottom) - { - _leftPad = left; _rightPad = right; _topPad = top; _bottomPad = bottom; - } - - void setLegendPos(uint32_t left, uint32_t top) - { _legendXPos = left; _legendYPos = top; } - - void printCanvas(std::ostream& out, uint32_t width, uint32_t height) const; - void printScript(std::ostream& out, const std::string& indent = "") const; -}; - -} // storage - diff --git a/storage/src/vespa/storage/storageutil/palette.cpp b/storage/src/vespa/storage/storageutil/palette.cpp deleted file mode 100644 index 31e2f9e426c..00000000000 --- a/storage/src/vespa/storage/storageutil/palette.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "palette.h" - -#include <iostream> -#include <iomanip> - -namespace storage { - -namespace { - struct Col { - int16_t red; - int16_t green; - int16_t blue; - - Col(int16_t r, int16_t g, int16_t b) : red(r), green(g), blue(b) {} - }; - - std::vector<Col> createMainColors() { - std::vector<Col> v; - v.push_back(Col(128, 128, 128)); - v.push_back(Col(255, 0, 0)); - v.push_back(Col(255, 255, 0)); - v.push_back(Col(255, 0, 255)); - v.push_back(Col(0, 255, 0)); - v.push_back(Col(0, 255, 255)); - v.push_back(Col(0, 0, 255)); - v.push_back(Col(128, 64, 192)); - v.push_back(Col(192, 128, 64)); - v.push_back(Col(64, 192, 128)); - return v; - } - - std::vector<Col> mainColors(createMainColors()); -} - -Palette::Palette(uint32_t colorCount) -{ - - uint32_t variations = (colorCount + mainColors.size() - 1) - / (mainColors.size()); - int16_t darkvars = variations / 2; - int16_t lightvars = (variations - 1) / 2; - - std::vector<Col> darkVars; - if (darkvars > 0) { - for (int32_t i=darkvars; i>0; --i) { - for (uint32_t j=0; j<mainColors.size(); ++j) { - Col& main(mainColors[j]); - int rdiff = main.red / (darkvars + 1); - int gdiff = main.green / (darkvars + 1); - int bdiff = main.blue / (darkvars + 1); - darkVars.push_back(Col( - std::max(0, main.red - rdiff * i), - std::max(0, main.green - gdiff * i), - std::max(0, main.blue - bdiff * i))); - } - } - } - std::vector<Col> lightVars; - if (lightvars > 0) { - for (int32_t i=1; i<=lightvars; ++i) { - for (uint32_t j=0; j<mainColors.size(); ++j) { - Col& main(mainColors[j]); - int rdiff = (255 - main.red) / (lightvars + 1); - int gdiff = (255 - main.green) / (lightvars + 1); - int bdiff = (255 - main.blue) / (lightvars + 1); - lightVars.push_back(Col( - std::min(255, main.red + rdiff * i), - std::min(255, main.green + gdiff * i), - std::min(255, main.blue + bdiff * i))); - } - } - } - for (std::vector<Col>::const_iterator it = darkVars.begin(); - it != darkVars.end(); ++it) - { - _colors.push_back((it->red << 16) | (it->green << 8) | it->blue); - } - for (std::vector<Col>::const_iterator it = mainColors.begin(); - it != mainColors.end(); ++it) - { - _colors.push_back((it->red << 16) | (it->green << 8) | it->blue); - } - for (std::vector<Col>::const_iterator it = lightVars.begin(); - it != lightVars.end(); ++it) - { - _colors.push_back((it->red << 16) | (it->green << 8) | it->blue); - } -} - -void -Palette::printHtmlTablePalette(std::ostream& out) const -{ - out << "<table>" << std::hex << std::setfill('0'); - uint32_t col = 0; - while (col < _colors.size()) { - out << "\n<tr>"; - for (uint32_t i=0; i<mainColors.size(); ++i) { - out << "\n <td bgcolor=\"#" << std::setw(6) << _colors[col++] - << "\">"; - for (uint32_t j=0; j<6; ++j) out << " "; - out << "</td>"; - } - out << "\n</tr>"; - } - out << "\n</table>" << std::dec; -} - -} // storage diff --git a/storage/src/vespa/storage/storageutil/palette.h b/storage/src/vespa/storage/storageutil/palette.h deleted file mode 100644 index e8db8a40aff..00000000000 --- a/storage/src/vespa/storage/storageutil/palette.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::Palette - * - * \brief Contains a set of distinct colors. - * - * When writing graphics like charts one wants to use distinct colors. - * This class defines some distinct colors. - */ - -#pragma once - -#include <vector> -#include <cstdint> -#include <iosfwd> - -namespace storage { - -class Palette { - std::vector<uint32_t> _colors; - -public: - Palette(uint32_t colorCount); - - uint32_t operator[](uint32_t colorIndex) const - { return _colors[colorIndex]; } - - void printHtmlTablePalette(std::ostream& out) const; -}; - -} // storage - diff --git a/storage/src/vespa/storage/storageutil/piechart.cpp b/storage/src/vespa/storage/storageutil/piechart.cpp deleted file mode 100644 index 60080a14ead..00000000000 --- a/storage/src/vespa/storage/storageutil/piechart.cpp +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "piechart.h" - -#include <iomanip> -#include <vespa/vespalib/util/exceptions.h> - -namespace storage { - -double PieChart::_minValue = 0.0000001; - -PieChart::Entry::Entry(double val, const std::string& name, int32_t col) - : _value(val), _name(name), _color(col) -{ -} - -void -PieChart::printHtmlHeadAdditions(std::ostream& out, const std::string& indent) -{ - (void) out; - (void) indent; - // FIXME this used to reference Yahoo-internal JS URIs. - // Deprecated functionality either way. -} - -PieChart::PieChart(const std::string& name, ColorScheme cs) - : _name(name), - _values(), - _colors(cs), - _printLabels(true) -{} - -PieChart::~PieChart() {} - -void -PieChart::add(double value, const std::string& name) -{ - if (value < _minValue) { - std::ostringstream ost; - ost << "Value of " << value << " is below the minimum supported value " - << "of the pie chart (" << _minValue << ")"; - throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC); - } - if (_colors == SCHEME_CUSTOM) { - throw vespalib::IllegalArgumentException( - "Using custom color scheme you need to supply a color for each " - "value.", VESPA_STRLOC); - } - _values.push_back(Entry(value, name, UNDEFINED)); -} - -void -PieChart::add(double value, const std::string& name, Color c) -{ - if (value < _minValue) { - std::ostringstream ost; - ost << "Value of " << value << " is below the minimum supported value " - << "of the pie chart (" << _minValue << ")"; - throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC); - } - if (_colors != SCHEME_CUSTOM) { - throw vespalib::IllegalArgumentException( - "Not using custom color scheme you cannot supply a custom " - "color for a value.", VESPA_STRLOC); - } - _values.push_back(Entry(value, name, c)); -} - -void -PieChart::add(double value, const std::string& name, int32_t color) -{ - if (value < _minValue) { - std::ostringstream ost; - ost << "Value of " << value << " is below the minimum supported value " - << "of the pie chart (" << _minValue << ")"; - throw vespalib::IllegalArgumentException(ost.str(), VESPA_STRLOC); - } - if (_colors != SCHEME_CUSTOM) { - throw vespalib::IllegalArgumentException( - "Not using custom color scheme you cannot supply a custom " - "color for a value.", VESPA_STRLOC); - } - _values.push_back(Entry(value, name, (Color) color)); -} - -void -PieChart::printCanvas(std::ostream& out, uint32_t width, uint32_t height) const -{ - out << "<div><canvas id=\"" << _name << "\" width=\"" << width - << "\" height=\"" << height << "\"/></div>"; -} - -namespace { - void printDatasetDefinition(std::ostream& o, const std::string& i, - const std::string& n, const std::vector<PieChart::Entry>& e) - { - o << i << " var " << n << "_dataset = {\n" << std::dec; - bool first = true; - for (std::vector<PieChart::Entry>::const_iterator it = e.begin(); - it != e.end(); ++it) - { - if (!first) o << ",\n"; - first = false; - o << i << " '" << it->_name << "': [[0," << it->_value - << "]]"; - } - o << "\n" << i << " };"; - } - - void printCustomColorScheme(std::ostream& o, const std::string& i, - const std::string& n, const std::vector<PieChart::Entry>& e) - { - o << " var " << n << "_customScheme = new Hash({\n" << std::hex; - bool first = true; - for (std::vector<PieChart::Entry>::const_iterator it = e.begin(); - it != e.end(); ++it) - { - if (!first) o << ",\n"; - first = false; - o << i << " '" << it->_name << "': '#" << std::setw(6) - << std::setfill('0') << (it->_color & 0x00FFFFFF) << "'"; - } - o << "\n" << i << " });" << std::dec; - } - - void printOptions(std::ostream& o, const std::string& i, - const std::string& n, const std::vector<PieChart::Entry>& e, - PieChart::ColorScheme c, bool printLabels) - { - o << " var " << n << "_options = {\n" - << i << " padding: {\n" - << i << " left: 0,\n" - << i << " right: 0,\n" - << i << " top: 0,\n" - << i << " bottom: 0,\n" - << i << " },\n" - << i << " background: {\n" - << i << " color: '#ffffff'\n" - << i << " },\n" - << i << " pieRadius: '0.4',\n"; - if (c == PieChart::SCHEME_CUSTOM) { - o << i << " \"colorScheme\": " << n << "_customScheme,\n"; - } else { - o << i << " colorScheme: '"; - switch (c) { - case PieChart::SCHEME_RED: o << "red"; break; - case PieChart::SCHEME_BLUE: o << "blue"; break; - case PieChart::SCHEME_CUSTOM: break; - } - o << "',\n"; - } - o << i << " axis: {\n" - << i << " labelColor: '#000000',\n" - << i << " x: {\n"; - if (!printLabels) { - o << i << " hide: true,\n"; - } - o << i << " ticks: [\n"; - bool first = true; - uint32_t tmp = 0; - for (std::vector<PieChart::Entry>::const_iterator it = e.begin(); - it != e.end(); ++it) - { - if (!first) o << ",\n"; - first = false; - o << i << " {v:" << tmp++ << ", label:'" << it->_name - << "'}"; - } - o << "\n" << i << " ]\n"; - o << i << " }\n" - << i << " }\n" - << i << " };"; - } - - void printPie(std::ostream& o, const std::string& i, const std::string& n) - { - o << " var " << n << "_pie = new Plotr.PieChart('" << n << "', " - << n << "_options);\n" - << i << " " << n << "_pie.addDataset(" << n << "_dataset);\n" - << i << " " << n << "_pie.render();"; - } -} - -void -PieChart::printScript(std::ostream& out, const std::string& indent) const -{ - out << "<script type=\"text/javascript\">\n"; - printDatasetDefinition(out, indent, _name, _values); - if (_colors == SCHEME_CUSTOM) { - out << "\n" << indent; - printCustomColorScheme(out, indent, _name, _values); - } - out << "\n" << indent; - printOptions(out, indent, _name, _values, _colors, _printLabels); - out << "\n" << indent; - printPie(out, indent, _name); - out << "\n" << indent << "</script>"; -} - -} // storage - diff --git a/storage/src/vespa/storage/storageutil/piechart.h b/storage/src/vespa/storage/storageutil/piechart.h deleted file mode 100644 index c0c26bdcb65..00000000000 --- a/storage/src/vespa/storage/storageutil/piechart.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. -/** - * \class storage::PieChart - * \ingroup util - * - * \brief Helper library to print pie charts in HTML. - */ - -#pragma once - -#include <string> -#include <vector> -#include <ostream> - -namespace storage { - -class PieChart { -public: - static double _minValue; - - enum ColorScheme { - SCHEME_CUSTOM, - SCHEME_RED, - SCHEME_BLUE - }; - enum Color { - UNDEFINED = -1, - BLACK = 0x000000, - RED = 0xFF0000, - GREEN = 0x00FF00, - BLUE = 0x0000FF, - WHITE = 0xFFFFFF - }; - struct Entry { - double _value; - std::string _name; - int32_t _color; - - Entry(double val, const std::string& name, int32_t col); - }; - - static void printHtmlHeadAdditions( - std::ostream& out, const std::string& indent = ""); - -private: - const std::string _name; - std::vector<Entry> _values; - ColorScheme _colors; - bool _printLabels; - -public: - PieChart(const std::string&, ColorScheme = SCHEME_BLUE); - ~PieChart(); - - void printLabels(bool doprint) { _printLabels = doprint; } - - void add(double value, const std::string& name); - void add(double value, const std::string& name, Color c); - void add(double value, const std::string& name, int32_t color); - - void printCanvas(std::ostream& out, uint32_t width, uint32_t height) const; - void printScript(std::ostream& out, const std::string& indent = "") const; -}; - -} // storage - diff --git a/storage/src/vespa/storage/visiting/visitor.cpp b/storage/src/vespa/storage/visiting/visitor.cpp index b91e38e035e..0d230745875 100644 --- a/storage/src/vespa/storage/visiting/visitor.cpp +++ b/storage/src/vespa/storage/visiting/visitor.cpp @@ -2,7 +2,6 @@ #include "visitor.h" #include "visitormetrics.h" -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageframework/generic/clock/timer.h> #include <vespa/storageapi/message/datagram.h> #include <vespa/storage/persistence/messages.h> @@ -267,8 +266,7 @@ Visitor::Visitor(StorageComponent& component) _id(), _controlDestination(), _dataDestination(), - _documentSelection(), - _memoryManager(0) + _documentSelection() { } @@ -603,10 +601,6 @@ Visitor::start(api::VisitorId id, api::StorageMessage::Id cmdId, _documentPriority = documentPriority; _state = STATE_RUNNING; - if (_memoryAllocType == 0) { - _memoryAllocType = &_component.getMemoryManager() - .getAllocationType("VISITOR_BUFFER"); - } LOG(debug, "Starting visitor '%s' for %" PRIu64 " buckets from %" PRIu64 " to " "%" PRIu64 ". First is %s. Max pending replies: %u, include " @@ -803,17 +797,7 @@ Visitor::onCreateIteratorReply( LOG(debug, "Visitor '%s' starting to visit bucket %s.", _id.c_str(), bucketId.toString().c_str()); - framework::MemoryToken::UP token( - _memoryManager->allocate( - *_memoryAllocType, _docBlockSize, _docBlockSize, _priority)); - if (token.get() == 0) { - // Not enough memory - return; - } - std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), bucket, - bucketState.getIteratorId(), - _docBlockSize)); + auto cmd = std::make_shared<GetIterCommand>(bucket, bucketState.getIteratorId(), _docBlockSize); cmd->setLoadType(_initiatingCmd->getLoadType()); cmd->getTrace().setLevel(_traceLevel); cmd->setPriority(_priority); @@ -1223,19 +1207,8 @@ Visitor::getIterators() it = _bucketStates.erase(it); continue; } - framework::MemoryToken::UP token( - _memoryManager->allocate( - *_memoryAllocType, _docBlockSize, _docBlockSize, - _priority)); - if (token.get() == 0) { - // Not enough memory - return true; - } - std::shared_ptr<GetIterCommand> cmd( - new GetIterCommand(std::move(token), - bucketState.getBucket(), - bucketState.getIteratorId(), - _docBlockSize)); + auto cmd = std::make_shared<GetIterCommand>( + bucketState.getBucket(), bucketState.getIteratorId(), _docBlockSize); cmd->setLoadType(_initiatingCmd->getLoadType()); cmd->getTrace().setLevel(_traceLevel); cmd->setPriority(_priority); diff --git a/storage/src/vespa/storage/visiting/visitor.h b/storage/src/vespa/storage/visiting/visitor.h index 4436312032f..f84b105f1ed 100644 --- a/storage/src/vespa/storage/visiting/visitor.h +++ b/storage/src/vespa/storage/visiting/visitor.h @@ -327,7 +327,6 @@ private: protected: // These variables should not be altered after visitor starts. This not // controlled by locks. - const framework::MemoryAllocationType* _memoryAllocType; VisitorMessageHandler* _messageHandler; VisitorMessageSession::UP _messageSession; documentapi::Priority::Value _documentPriority; @@ -339,7 +338,6 @@ protected: std::string _documentSelectionString; std::unique_ptr<document::OrderingSpecification> _ordering; vdslib::VisitorStatistics _visitorStatistics; - framework::MemoryManagerInterface* _memoryManager; bool isCompletedCalled() const { return _calledCompletedVisitor; } @@ -365,8 +363,6 @@ public: const api::StorageMessageAddress* getDataDestination() const { return _dataDestination.get(); } // Can't be null if attached - void setAllocationType(const framework::MemoryAllocationType& mat) - { _memoryAllocType = &mat; } void setMaxPending(unsigned int maxPending) { _visitorOptions._maxPending = maxPending; } @@ -384,8 +380,6 @@ public: { _docBlockTimeout = timeout; } void setVisitorInfoTimeout(framework::MilliSecTime timeout) { _visitorInfoTimeout = timeout; } - void setMemoryManager(framework::MemoryManagerInterface& mm) - { _memoryManager = &mm; } void setOwnNodeIndex(uint16_t nodeIndex) { _ownNodeIndex = nodeIndex; } void setBucketSpace(document::BucketSpace bucketSpace) { _bucketSpace = bucketSpace; } diff --git a/storage/src/vespa/storage/visiting/visitormanager.cpp b/storage/src/vespa/storage/visiting/visitormanager.cpp index deb38bb927d..f207c4436b1 100644 --- a/storage/src/vespa/storage/visiting/visitormanager.cpp +++ b/storage/src/vespa/storage/visiting/visitormanager.cpp @@ -7,7 +7,6 @@ #include "countvisitor.h" #include "testvisitor.h" #include "recoveryvisitor.h" -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storage/common/statusmessages.h> #include <vespa/config/common/exceptions.h> #include <vespa/documentapi/loadtypes/loadtypeset.h> @@ -46,10 +45,6 @@ VisitorManager::VisitorManager(const config::ConfigUri & configUri, _enforceQueueUse(false), _visitorFactories(externalFactories) { - _component.getMemoryManager().registerAllocationType( - framework::MemoryAllocationType( - "VISITOR_BUFFER", - framework::MemoryAllocationType::EXTERNAL_LOAD)); _configFetcher.subscribe<vespa::config::content::core::StorVisitorConfig>(configUri.getConfigId(), this); _configFetcher.start(); _component.registerMetric(*_metrics); diff --git a/storage/src/vespa/storage/visiting/visitorthread.cpp b/storage/src/vespa/storage/visiting/visitorthread.cpp index d3fed86b741..4743ac66054 100644 --- a/storage/src/vespa/storage/visiting/visitorthread.cpp +++ b/storage/src/vespa/storage/visiting/visitorthread.cpp @@ -2,7 +2,6 @@ #include "visitorthread.h" #include "messages.h" -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/document/repo/documenttyperepo.h> #include <vespa/document/select/bodyfielddetector.h> #include <vespa/document/select/orderingselector.h> @@ -100,9 +99,7 @@ VisitorThread::VisitorThread(uint32_t threadIndex, _timeBetweenTicks(1000), _component(componentRegister, getThreadName(threadIndex)), _messageSessionFactory(messageSessionFac), - _visitorFactories(visitorFactories), - _memoryBufferAlloc( - _component.getMemoryManager().getAllocationType("VISITOR_BUFFER")) + _visitorFactories(visitorFactories) { framework::MilliSecTime maxProcessingTime(30 * 1000); framework::MilliSecTime waitTime(1000); @@ -473,8 +470,6 @@ VisitorThread::onCreateVisitor( cmd->getInstanceId().c_str(), errors.str().c_str()); break; } - visitor->setAllocationType(_memoryBufferAlloc); - visitor->setMemoryManager(_component.getMemoryManager()); // Set visitor parameters if (cmd->getMaximumPendingReplyCount() != 0) { visitor->setMaxPending(cmd->getMaximumPendingReplyCount()); diff --git a/storage/src/vespa/storage/visiting/visitorthread.h b/storage/src/vespa/storage/visiting/visitorthread.h index 1f1459ccb2b..97ca70f3761 100644 --- a/storage/src/vespa/storage/visiting/visitorthread.h +++ b/storage/src/vespa/storage/visiting/visitorthread.h @@ -90,7 +90,6 @@ class VisitorThread : public framework::Runnable, framework::Thread::UP _thread; VisitorMessageSessionFactory& _messageSessionFactory; VisitorFactory::Map& _visitorFactories; - const framework::MemoryAllocationType& _memoryBufferAlloc; public: VisitorThread(uint32_t threadIndex, diff --git a/storageapi/src/vespa/storageapi/messageapi/storagemessage.h b/storageapi/src/vespa/storageapi/messageapi/storagemessage.h index 663a0ccdf4e..32f6c769e87 100644 --- a/storageapi/src/vespa/storageapi/messageapi/storagemessage.h +++ b/storageapi/src/vespa/storageapi/messageapi/storagemessage.h @@ -12,7 +12,6 @@ #pragma once #include "messagehandler.h" -#include <vespa/storageframework/generic/memory/memorytoken.h> #include <vespa/documentapi/loadtypes/loadtype.h> #include <vespa/messagebus/routing/route.h> #include <vespa/messagebus/trace.h> @@ -335,7 +334,6 @@ private: StorageMessage(const StorageMessage&); mutable std::unique_ptr<TransportContext> _transportContext; - std::unique_ptr<framework::MemoryToken> _memoryToken; protected: static Id generateMsgId(); @@ -375,10 +373,6 @@ public: void setAddress(const StorageMessageAddress& address) { _address.reset(new StorageMessageAddress(address)); } - void setMemoryToken(std::unique_ptr<framework::MemoryToken> token) { - _memoryToken = std::move(token); - } - /** Returns the approximate memory footprint of a storage message. By default, returns 50 bytes. This only needs to be overriden if the diff --git a/storageframework/CMakeLists.txt b/storageframework/CMakeLists.txt index 5a4b2e02372..a3414a8b05c 100644 --- a/storageframework/CMakeLists.txt +++ b/storageframework/CMakeLists.txt @@ -11,12 +11,10 @@ vespa_define_module( src/vespa/storageframework/defaultimplementation src/vespa/storageframework/defaultimplementation/clock src/vespa/storageframework/defaultimplementation/component - src/vespa/storageframework/defaultimplementation/memory src/vespa/storageframework/defaultimplementation/thread src/vespa/storageframework/generic src/vespa/storageframework/generic/clock src/vespa/storageframework/generic/component - src/vespa/storageframework/generic/memory src/vespa/storageframework/generic/metric src/vespa/storageframework/generic/status src/vespa/storageframework/generic/thread @@ -28,7 +26,6 @@ vespa_define_module( TESTS src/tests src/tests/clock - src/tests/memory src/tests/status src/tests/thread ) diff --git a/storageframework/src/tests/CMakeLists.txt b/storageframework/src/tests/CMakeLists.txt index eb6d45330e8..0d604d64f74 100644 --- a/storageframework/src/tests/CMakeLists.txt +++ b/storageframework/src/tests/CMakeLists.txt @@ -5,7 +5,6 @@ vespa_add_executable(storageframework_testrunner_app TEST DEPENDS storageframework_testclock storageframework_teststatus - storageframework_testmemory storageframework_testthread ) diff --git a/storageframework/src/tests/memory/.gitignore b/storageframework/src/tests/memory/.gitignore deleted file mode 100644 index 7e7c0fe7fae..00000000000 --- a/storageframework/src/tests/memory/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/.depend -/Makefile diff --git a/storageframework/src/tests/memory/CMakeLists.txt b/storageframework/src/tests/memory/CMakeLists.txt deleted file mode 100644 index 591a78f1046..00000000000 --- a/storageframework/src/tests/memory/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(storageframework_testmemory - SOURCES - memorymanagertest.cpp - memorystatetest.cpp - DEPENDS - storageframework -) diff --git a/storageframework/src/tests/memory/memorymanagertest.cpp b/storageframework/src/tests/memory/memorymanagertest.cpp deleted file mode 100644 index 5424b8dea82..00000000000 --- a/storageframework/src/tests/memory/memorymanagertest.cpp +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/storageframework/defaultimplementation/clock/realclock.h> -#include <vespa/storageframework/defaultimplementation/memory/memorymanager.h> -#include <vespa/storageframework/defaultimplementation/memory/simplememorylogic.h> -#include <vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h> -#include <vespa/vdstestlib/cppunit/macros.h> -#include <vespa/vespalib/util/document_runnable.h> -#include <vespa/vespalib/util/random.h> - -namespace storage { -namespace framework { -namespace defaultimplementation { - -struct MemoryManagerTest : public CppUnit::TestFixture -{ - void testBasics(); - void testCacheAllocation(); - void testStress(); - - CPPUNIT_TEST_SUITE(MemoryManagerTest); - CPPUNIT_TEST(testBasics); - CPPUNIT_TEST(testCacheAllocation); - CPPUNIT_TEST(testStress); - CPPUNIT_TEST_SUITE_END(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION(MemoryManagerTest); - -void -MemoryManagerTest::testBasics() -{ - uint64_t maxMemory = 1000; - RealClock clock; - SimpleMemoryLogic* logic = new SimpleMemoryLogic(clock, maxMemory); - AllocationLogic::UP allLogic(std::move(logic)); - MemoryManager manager(std::move(allLogic)); - - const MemoryAllocationType& putAlloc(manager.registerAllocationType( - MemoryAllocationType("put", MemoryAllocationType::EXTERNAL_LOAD))); - const MemoryAllocationType& getAlloc(manager.registerAllocationType( - MemoryAllocationType("get", MemoryAllocationType::EXTERNAL_LOAD))); - const MemoryAllocationType& bufAlloc(manager.registerAllocationType( - MemoryAllocationType("buffer"))); - const MemoryAllocationType& cacheAlloc(manager.registerAllocationType( - MemoryAllocationType("cache", MemoryAllocationType::CACHE))); - const MemoryState& state(logic->getState()); - const MemoryState::SnapShot& current(state.getCurrentSnapshot()); - // Basics - { - // * Getting a token, and release it back with correct behavior - framework::MemoryToken::UP put = manager.allocate(putAlloc, - 0, 100, 80); - CPPUNIT_ASSERT(put.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(100), put->getSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(100), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(900), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - - // * Do the same while not being empty. Different type. - framework::MemoryToken::UP get = manager.allocate(getAlloc, - 30, 200, 50); - CPPUNIT_ASSERT(get.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(200), get->getSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(300), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(700), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - - // * Do the same while not being empty. Same type. - framework::MemoryToken::UP get2 = manager.allocate( - getAlloc, - 70, - 150, - 60); - - CPPUNIT_ASSERT(get2.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(150), get2->getSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(450), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(550), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - } - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount()); - - // Non-external load - // * Getting minimum when going beyond 80% full - { - framework::MemoryToken::UP filler = manager.allocate(putAlloc, - 795, 795, 90); - framework::MemoryToken::UP resize = manager.allocate( - bufAlloc, 10, 90, 80); - CPPUNIT_ASSERT(resize.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(10), resize->getSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(805), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(195), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - } - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount()); - - // Non-external load - // * Getting up to threshold if hitting it - { - framework::MemoryToken::UP filler = manager.allocate(putAlloc, - 750, 750, 90); - framework::MemoryToken::UP resize = manager.allocate( - bufAlloc, 10, 90, 80); - CPPUNIT_ASSERT(resize.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(50), resize->getSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(800), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(200), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - } - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount()); - - // External load - { - // * Stopped when going beyond 80% full - framework::MemoryToken::UP filler = manager.allocate(putAlloc, - 795, 795, 90); - framework::MemoryToken::UP put = manager.allocate(putAlloc, - 10, 100, 80); - CPPUNIT_ASSERT(put.get() == 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(795), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(205), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - } - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount()); - - // External load - { - // * Getting up to threshold if hitting it - framework::MemoryToken::UP filler = manager.allocate(putAlloc, - 750, 750, 90); - framework::MemoryToken::UP put = manager.allocate(putAlloc, - 10, 100, 80); - CPPUNIT_ASSERT(put.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(50), put->getSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(800), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(200), state.getFreeSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(1000), state.getTotalSize()); - } - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount()); - - // Test caching.. - { - // Cache paradigm: - // Allocate a token taking up no space at all. - // Give it to your ReduceMemoryUsageInterface implementation. - // Run resize on your token in that implementation to get memory and - // return memory. That way locking should be easy when needed. - struct ReduceI : public framework::ReduceMemoryUsageInterface { - framework::MemoryToken::UP _token; - - uint64_t reduceMemoryConsumption(const MemoryToken& token, uint64_t reduceBy) override { - assert(&token == _token.get()); - (void) &token; - assert(_token->getSize() >= reduceBy); - return reduceBy; - } - }; - ReduceI reducer; - framework::MemoryToken::UP cache = manager.allocate(cacheAlloc, - 0, 0, 0, &reducer); - CPPUNIT_ASSERT(cache.get() != 0); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), cache->getSize()); - reducer._token = std::move(cache); - for (uint32_t i=1; i<=50; ++i) { - bool success = reducer._token->resize(i * 10, i * 10); - CPPUNIT_ASSERT_EQUAL(true, success); - } - CPPUNIT_ASSERT_EQUAL(uint64_t(500), reducer._token->getSize()); - - // * Ordered to free space - framework::MemoryToken::UP put = manager.allocate(putAlloc, - 600, 600, 80); - CPPUNIT_ASSERT_EQUAL_MSG(manager.toString(), - uint64_t(400), reducer._token->getSize()); - CPPUNIT_ASSERT_EQUAL_MSG(manager.toString(), - uint64_t(600), put->getSize()); - } - CPPUNIT_ASSERT_EQUAL_MSG(state.toString(true), - uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL_MSG(state.toString(true), - uint64_t(0), current.getUserCount()); - - // Test merge and tracking of allocation counts with merge, by doing - // operations with tokens and see that user count and used size - // correctly go back to zero. - { - framework::MemoryToken::UP tok1( - manager.allocate(putAlloc, 5, 5, 40)); - framework::MemoryToken::UP tok2( - manager.allocate(putAlloc, 10, 10, 40)); - framework::MemoryToken::UP tok3( - manager.allocate(putAlloc, 20, 20, 40)); - framework::MemoryToken::UP tok4( - manager.allocate(putAlloc, 40, 40, 40)); - framework::MemoryToken::UP tok5( - manager.allocate(putAlloc, 80, 80, 40)); - framework::MemoryToken::UP tok6( - manager.allocate(putAlloc, 1, 1, 40)); - framework::MemoryToken::UP tok7( - manager.allocate(putAlloc, 3, 3, 40)); - } -} - -void -MemoryManagerTest::testCacheAllocation() -{ - uint64_t maxMemory = 3000; - - RealClock clock; - SimpleMemoryLogic::UP logic(new PriorityMemoryLogic(clock, maxMemory)); - logic->setCacheThreshold(1.0); - - AllocationLogic::UP allLogic(std::move(logic)); - MemoryManager manager(std::move(allLogic)); - - const MemoryAllocationType& putAlloc(manager.registerAllocationType( - MemoryAllocationType("put", MemoryAllocationType::EXTERNAL_LOAD))); - const MemoryAllocationType& cacheAlloc(manager.registerAllocationType( - MemoryAllocationType("cache", MemoryAllocationType::CACHE))); - - framework::MemoryToken::UP token = - manager.allocate(putAlloc, - 50, - 50, - 127); - - CPPUNIT_ASSERT_EQUAL(50, (int)token->getSize()); - - framework::MemoryToken::UP token2 = - manager.allocate(cacheAlloc, - 1000, - 2000, - 127); - - CPPUNIT_ASSERT_EQUAL(2000, (int)token2->getSize()); - - token2->resize(2000, 3000); - - CPPUNIT_ASSERT_EQUAL(2950, (int)token2->getSize()); -} - -namespace { -struct MemoryManagerLoadGiver : public document::Runnable, - public ReduceMemoryUsageInterface -{ - MemoryManager& _manager; - const framework::MemoryAllocationType& _type; - uint8_t _priority; - uint32_t _minMem; - uint32_t _maxMem; - uint32_t _failed; - uint32_t _ok; - uint32_t _reduced; - using MemoryTokenUP = std::unique_ptr<MemoryToken>; - std::vector<MemoryTokenUP> _tokens; - vespalib::Lock _cacheLock; - - MemoryManagerLoadGiver( - MemoryManager& manager, - const framework::MemoryAllocationType& type, - uint8_t priority, - uint32_t minMem, - uint32_t maxMem, - uint32_t tokensToKeep) - : _manager(manager), - _type(type), - _priority(priority), - _minMem(minMem), - _maxMem(maxMem), - _failed(0), - _ok(0), - _reduced(0), - _tokens(tokensToKeep) - { - } - - uint64_t reduceMemoryConsumption(const MemoryToken&, uint64_t reduceBy) override { - ++_reduced; - return reduceBy; - } - - void run() override { - ReduceMemoryUsageInterface* reducer = 0; - if (_type.isCache()) reducer = this; - vespalib::RandomGen randomizer; - while (running()) { - vespalib::Lock lock(_cacheLock); - framework::MemoryToken::UP token = _manager.allocate( - _type, _minMem, _maxMem, _priority, reducer); - if (token.get() == 0) { - ++_failed; - } else { - ++_ok; - } - uint32_t index = randomizer.nextUint32(0, _tokens.size() - 1); - _tokens[index] = MemoryTokenUP(token.release()); - } - } -}; -} - -void -MemoryManagerTest::testStress() -{ - uint64_t stressTimeMS = 1 * 1000; - uint64_t maxMemory = 1 * 1024 * 1024; - RealClock clock; - AllocationLogic::UP logic(new PriorityMemoryLogic(clock, maxMemory)); - MemoryManager manager(std::move(logic)); - - FastOS_ThreadPool pool(128 * 1024); - std::vector<MemoryManagerLoadGiver*> loadGivers; - for (uint32_t type = 0; type < 5; ++type) { - const MemoryAllocationType* allocType = 0; - uint32_t min = 1000, max = 5000; - if (type == 0) { - allocType = &manager.registerAllocationType(MemoryAllocationType( - "default")); - } else if (type == 1) { - allocType = &manager.registerAllocationType(MemoryAllocationType( - "external", MemoryAllocationType::EXTERNAL_LOAD)); - } else if (type == 2) { - allocType = &manager.registerAllocationType(MemoryAllocationType( - "forced", MemoryAllocationType::FORCE_ALLOCATE)); - } else if (type == 3) { - allocType = &manager.registerAllocationType(MemoryAllocationType( - "forcedExternal", MemoryAllocationType::FORCE_ALLOCATE - | MemoryAllocationType::EXTERNAL_LOAD)); - } else if (type == 4) { - allocType = &manager.registerAllocationType(MemoryAllocationType( - "cache", MemoryAllocationType::CACHE)); - max = 30000; - } - for (int priority = 0; priority < 256; priority += 8) { - loadGivers.push_back(new MemoryManagerLoadGiver( - manager, *allocType, priority, min, max, 10)); - loadGivers.back()->start(pool); - } - FastOS_Thread::Sleep(stressTimeMS); - } - FastOS_Thread::Sleep(5 * stressTimeMS); - uint64_t okTotal = 0, failedTotal = 0, reducedTotal = 0; - for (uint32_t i = 0; i < loadGivers.size(); i++) { - /* - fprintf(stderr, "%d %s-%u: Failed %d, ok %d, reduced %d\n", - i, loadGivers[i]->_type.getName().c_str(), - uint32_t(loadGivers[i]->_priority), - loadGivers[i]->_failed, loadGivers[i]->_ok, - loadGivers[i]->_reduced); // */ - okTotal += loadGivers[i]->_ok; - failedTotal += loadGivers[i]->_failed; - reducedTotal += loadGivers[i]->_reduced; - } - for (uint32_t i = 0; i < loadGivers.size(); i++) loadGivers[i]->stop(); - for (uint32_t i = 0; i < loadGivers.size(); i++) loadGivers[i]->join(); - pool.Close(); - - /* - bool verbose = false; - std::cerr << "\n\nMemory allocations at end of load:\n"; - manager.print(std::cerr, verbose, ""); // */ - - for (uint32_t i = 0; i < loadGivers.size(); i++) { - loadGivers[i]->_tokens.clear(); - } - for (uint32_t i = 0; i < loadGivers.size(); i++) { - delete loadGivers[i]; - } - loadGivers.clear(); - - //std::cerr << "\n\nMemory allocations at end of testl:\n"; - //manager.print(std::cerr, verbose, ""); - - std::cerr << "\n Managed " << std::fixed - << (okTotal / (stressTimeMS / 1000)) - << " ok, " << (failedTotal / (stressTimeMS / 1000)) - << " failed and " << (reducedTotal / (stressTimeMS / 1000)) - << " reduced allocations/s.\n "; - - MemoryState state(clock, 1); - manager.getState(state); - const MemoryState::SnapShot& current(state.getCurrentSnapshot()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUserCount()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSize()); - CPPUNIT_ASSERT_EQUAL(uint64_t(0), current.getUsedSizeIgnoringCache()); -} - -} // defaultimplementation -} // framework -} // storage diff --git a/storageframework/src/tests/memory/memorystatetest.cpp b/storageframework/src/tests/memory/memorystatetest.cpp deleted file mode 100644 index cd565718632..00000000000 --- a/storageframework/src/tests/memory/memorystatetest.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/storageframework/defaultimplementation/clock/realclock.h> -#include <vespa/storageframework/defaultimplementation/memory/memorystate.h> -#include <vespa/vdstestlib/cppunit/macros.h> -#include <vespa/vespalib/util/exceptions.h> - -namespace storage { -namespace framework { -namespace defaultimplementation { - -struct MemoryStateTest : public CppUnit::TestFixture -{ - - void testBasics(); - - CPPUNIT_TEST_SUITE(MemoryStateTest); - CPPUNIT_TEST(testBasics); // Fails sometimes, test needs rewrite. - CPPUNIT_TEST_SUITE_END(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION(MemoryStateTest); - -class SimpleMemoryManager : public framework::MemoryManagerInterface -{ -private: - std::map<std::string, framework::MemoryAllocationType> _types; - -public: - void setMaximumMemoryUsage(uint64_t max) override { (void) max; } - - const framework::MemoryAllocationType& - registerAllocationType(const framework::MemoryAllocationType& type) override { - _types[type.getName()] = type; - return _types[type.getName()]; - } - - const framework::MemoryAllocationType& - getAllocationType(const std::string& name) const override { - std::map<std::string, framework::MemoryAllocationType>::const_iterator iter = - _types.find(name); - - if (iter == _types.end()) { - throw vespalib::IllegalArgumentException("Allocation type not found: " + name); - } - - return iter->second; - } - - std::vector<const MemoryAllocationType*> getAllocationTypes() const override { - std::vector<const MemoryAllocationType*> types; - for(std::map<std::string, framework::MemoryAllocationType> - ::const_iterator it = _types.begin(); it != _types.end(); ++it) - { - types.push_back(&it->second); - } - return types; - } - - framework::MemoryToken::UP allocate(const framework::MemoryAllocationType&, - uint64_t, - uint64_t, - uint8_t, - framework::ReduceMemoryUsageInterface*) override - { - return framework::MemoryToken::UP(); - } - - uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override { - (void) priority; - return 0; - } -}; - -void -MemoryStateTest::testBasics() -{ - SimpleMemoryManager manager; - - const MemoryAllocationType& putAlloc(manager.registerAllocationType( - MemoryAllocationType("MESSAGE_PUT", MemoryAllocationType::EXTERNAL_LOAD))); - const MemoryAllocationType& getAlloc(manager.registerAllocationType( - MemoryAllocationType("MESSAGE_GET", MemoryAllocationType::EXTERNAL_LOAD))); - const MemoryAllocationType& blockAlloc(manager.registerAllocationType( - MemoryAllocationType("MESSAGE_DOCBLOCK"))); - const MemoryAllocationType& databaseAlloc(manager.registerAllocationType( - MemoryAllocationType("DATABASE"))); - const MemoryAllocationType& cacheAlloc(manager.registerAllocationType( - MemoryAllocationType("SLOTFILE_CACHE", MemoryAllocationType::CACHE))); - - uint32_t maxMemory = 1024; - - RealClock clock; - MemoryState state1(clock, maxMemory); - MemoryState state2(clock, maxMemory); - - state1.setMinJumpToUpdateMax(50); - - state1.addToEntry(putAlloc, 100, 10, - MemoryState::GOT_MAX, false); - state1.addToEntry(putAlloc, 100, 60, - MemoryState::GOT_MAX, false); - state1.addToEntry(blockAlloc, - 200, 20, - MemoryState::GOT_MIN, false); - state1.addToEntry(getAlloc, 0, 15, - MemoryState::DENIED, false, 0); - state1.addToEntry(databaseAlloc, 150, 0, - MemoryState::DENIED, true, 1); - state1.addToEntry(cacheAlloc, 45, 0, - MemoryState::GOT_MAX, true, 1); - - state2.addToEntry(putAlloc, 50, 10, - MemoryState::GOT_MIN, false); - state2.addToEntry(putAlloc, 20, 40, - MemoryState::GOT_MIN, false); - - state1.removeFromEntry(databaseAlloc, 25, 0, 0); - state1.removeFromEntry(putAlloc, 100, 60); - - MemoryState::SnapShot state3; - state3 = state1.getMaxSnapshot(); - state3 += state2.getMaxSnapshot(); - - std::string expected; - expected = - "\n" - "MemoryState(Max memory: 1024) {\n" - " Current: SnapShot(Used 470, w/o cache 425) {\n" - " Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n" - " DATABASE(0): Used(125 B / 1) Stats(1, 0, 0, 1, 1)\n" - " MESSAGE_DOCBLOCK(20): Used(200 B / 1) Stats(1, 0, 1, 0, 0)\n" - " MESSAGE_GET(15): Used(0 B / 0) Stats(1, 0, 0, 1, 0)\n" - " MESSAGE_PUT(10): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n" - " MESSAGE_PUT(60): Used(0 B / 0) Stats(1, 1, 0, 0, 0)\n" - " SLOTFILE_CACHE(0): Used(45 B / 1) Stats(1, 1, 0, 0, 1)\n" - " }\n" - " Max: SnapShot(Used 550, w/o cache 550) {\n" - " Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n" - " DATABASE(0): Used(150 B / 1) Stats(1, 0, 0, 1, 1)\n" - " MESSAGE_DOCBLOCK(20): Used(200 B / 1) Stats(1, 0, 1, 0, 0)\n" - " MESSAGE_GET(15): Used(0 B / 0) Stats(1, 0, 0, 1, 0)\n" - " MESSAGE_PUT(10): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n" - " MESSAGE_PUT(60): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n" - " }\n" - "}"; - - CPPUNIT_ASSERT_EQUAL(expected, "\n" + state1.toString(true)); - expected = "\n" -"MemoryState(Max memory: 1024) {\n" -" Current: SnapShot(Used 70, w/o cache 70) {\n" -" Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n" -" MESSAGE_PUT(10): Used(50 B / 1) Stats(1, 0, 1, 0, 0)\n" -" MESSAGE_PUT(40): Used(20 B / 1) Stats(1, 0, 1, 0, 0)\n" -" }\n" -"}"; - CPPUNIT_ASSERT_EQUAL(expected, "\n" + state2.toString(true)); - expected = "\n" -"SnapShot(Used 550, w/o cache 550) {\n" -" Type(Pri): Used(Size/Allocs) Stats(Allocs, Wanted, Min, Denied, Forced)\n" -" DATABASE(0): Used(150 B / 1) Stats(1, 0, 0, 1, 1)\n" -" MESSAGE_DOCBLOCK(20): Used(200 B / 1) Stats(1, 0, 1, 0, 0)\n" -" MESSAGE_GET(15): Used(0 B / 0) Stats(1, 0, 0, 1, 0)\n" -" MESSAGE_PUT(10): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n" -" MESSAGE_PUT(60): Used(100 B / 1) Stats(1, 1, 0, 0, 0)\n" -"}"; - CPPUNIT_ASSERT_EQUAL(expected, "\n" + state3.toString(true)); -} - -} // defaultimplementation -} // framework -} // storage diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt b/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt index 25e7a9812cf..ea6a7e15543 100644 --- a/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt +++ b/storageframework/src/vespa/storageframework/defaultimplementation/CMakeLists.txt @@ -3,7 +3,6 @@ vespa_add_library(storageframework_defaultimplementation SOURCES $<TARGET_OBJECTS:storageframework_clockimpl> $<TARGET_OBJECTS:storageframework_componentimpl> - $<TARGET_OBJECTS:storageframework_memoryimpl> $<TARGET_OBJECTS:storageframework_threadimpl> INSTALL lib64 DEPENDS diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp index a03f79b008d..84b091d25e3 100644 --- a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp +++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.cpp @@ -63,16 +63,6 @@ ComponentRegisterImpl::setMetricManager(metrics::MetricManager& mm) } void -ComponentRegisterImpl::setMemoryManager(MemoryManagerInterface& mm) -{ - vespalib::LockGuard lock(_componentLock); - _memoryManager = &mm; - for (uint32_t i=0; i<_components.size(); ++i) { - _components[i]->setMemoryManager(mm); - } -} - -void ComponentRegisterImpl::setClock(Clock& c) { vespalib::LockGuard lock(_componentLock); diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h index 7319a1e7d57..3e3dd08e1df 100644 --- a/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h +++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/componentregisterimpl.h @@ -71,7 +71,6 @@ public: void requestShutdown(vespalib::stringref reason) override; void setMetricManager(metrics::MetricManager&); - void setMemoryManager(MemoryManagerInterface&); void setClock(Clock&); void setThreadPool(ThreadPool&); void setUpgradeFlag(UpgradeFlags flag); diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp index 0f4d0ebe070..5efd638ec26 100644 --- a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp +++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.cpp @@ -7,13 +7,9 @@ namespace storage::framework::defaultimplementation { TestComponentRegister::TestComponentRegister(ComponentRegisterImpl::UP compReg) : _compReg(std::move(compReg)), _clock(), - _threadPool(_clock), - _memoryManager() + _threadPool(_clock) { assert(_compReg.get() != 0); - // Set a memory manager, so users can register memory types and - // ask for memory. - _compReg->setMemoryManager(_memoryManager); // Set a fake clock, giving test control of clock _compReg->setClock(_clock); // Set a thread pool so components can make threads in tests. diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h index 5226765dc40..4983d5dcdfc 100644 --- a/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h +++ b/storageframework/src/vespa/storageframework/defaultimplementation/component/testcomponentregister.h @@ -15,7 +15,6 @@ #include "componentregisterimpl.h" #include <vespa/storageframework/defaultimplementation/thread/threadpoolimpl.h> -#include <vespa/storageframework/defaultimplementation/memory/nomemorymanager.h> #include <vespa/storageframework/defaultimplementation/clock/fakeclock.h> namespace storage::framework::defaultimplementation { @@ -24,7 +23,6 @@ class TestComponentRegister { ComponentRegisterImpl::UP _compReg; FakeClock _clock; ThreadPoolImpl _threadPool; - NoMemoryManager _memoryManager; public: TestComponentRegister(ComponentRegisterImpl::UP compReg); @@ -34,7 +32,6 @@ public: FakeClock& getClock() { return _clock; } ThreadPoolImpl& getThreadPoolImpl() { return _threadPool; } FastOS_ThreadPool& getThreadPool() { return _threadPool.getThreadPool(); } - NoMemoryManager& getMemoryManager() { return _memoryManager; } }; } diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore b/storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore deleted file mode 100644 index 7e7c0fe7fae..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/.depend -/Makefile diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt b/storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt deleted file mode 100644 index e0aa10a6716..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(storageframework_memoryimpl OBJECT - SOURCES - memorymanager.cpp - simplememorylogic.cpp - memorystate.cpp - prioritymemorylogic.cpp - nomemorymanager.cpp - DEPENDS -) diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h deleted file mode 100644 index 5af2c9ff929..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/emptymemorylogic.h +++ /dev/null @@ -1,48 +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 <storage/memorymanager/memorymanager.h> -#include <storage/memorymanager/memorystate.h> - -namespace storage { - -class EmptyMemoryLogic : public AllocationLogic -{ -private: - MemoryState _state; - -public: - EmptyMemoryLogic() : _state(100) {} - - virtual void getState(MemoryState& state, bool resetMax) { - state = _state; - if (resetMax) _state.resetMax(); - } - - virtual MemoryToken::UP allocate( - const AllocationType& type, - uint64_t, uint64_t max, - storage::api::StorageMessage::Priority p, - ReduceMemoryUsageInterface* = 0) { - return MemoryToken::UP( - new MemoryToken(*this, type, max, p)); - } - - virtual bool resize(MemoryToken& token, uint64_t min, uint64_t max) { - setTokenSize(token, max); - return true; - } - - virtual void freeToken(MemoryToken&) {} - - virtual void print(std::ostream& out, bool verbose, - const std::string& indent) const - { - (void) verbose; (void) indent; - out << "EmptyMemoryLogic()"; - } -}; - -} // storage - diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp deleted file mode 100644 index 5af222d4b36..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "memorymanager.h" -#include "memorystate.h" -#include <vespa/vespalib/util/exceptions.h> - -namespace storage { -namespace framework { -namespace defaultimplementation { - -MemoryTokenImpl::MemoryTokenImpl(AllocationLogic& logic, - const MemoryAllocationType& type, - uint64_t allocated, - uint8_t p, - ReduceMemoryUsageInterface* reducer) - : _logic(logic), - _reducer(reducer), - _currentlyAllocated(allocated), - _allocCount(1), - _type(type), - _priority(p) -{ -} - -MemoryTokenImpl::~MemoryTokenImpl() -{ - _logic.freeToken(*this); -} - -bool -MemoryTokenImpl::resize(uint64_t min, uint64_t max) -{ - return _logic.resize(*this, min, max); -} - -void -MemoryTokenImpl::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - (void) verbose; (void) indent; - out << "MemoryToken(" << _type.getName() << ": Allocated(" << _allocCount << " - " - << _currentlyAllocated << ")"; -} - -AllocationLogic::~AllocationLogic() -{ -} - -MemoryToken::UP -AllocationLogic::allocate(const MemoryAllocationType& type, - uint64_t min, - uint64_t max, - uint8_t priority, - ReduceMemoryUsageInterface* reducer) -{ - MemoryToken::UP token(allocate(type, priority, reducer)); - assert(token.get()); - if (!resize(*token, min, max, 1)) token.reset(); - return token; -} - -bool -AllocationLogic::resize(MemoryTokenImpl& token, uint64_t min, uint64_t max) -{ - return resize(token, min, max, 0); -} - -MemoryManager::MemoryManager(AllocationLogic::UP logic) - : _logic(std::move(logic)) -{ - if (_logic.get() == 0) { - throw vespalib::IllegalArgumentException( - "Needs a real logic class to run. (Got null pointer)", - VESPA_STRLOC); - } -} - -MemoryManager::~MemoryManager() -{ -} - -void -MemoryManager::setMaximumMemoryUsage(uint64_t max) -{ - _logic->setMaximumMemoryUsage(max); -} - -void -MemoryManager::getState(MemoryState& state, bool resetMax) -{ - return _logic->getState(state, resetMax); -} - -const MemoryAllocationType& -MemoryManager::registerAllocationType(const MemoryAllocationType& type) -{ - vespalib::LockGuard lock(_typeLock); - _types[type.getName()] = MemoryAllocationType::UP( - new MemoryAllocationType(type)); - return *_types[type.getName()]; -} - -const MemoryAllocationType& -MemoryManager::getAllocationType(const std::string& name) const -{ - vespalib::LockGuard lock(_typeLock); - std::map<std::string, MemoryAllocationType::UP>::const_iterator it( - _types.find(name)); - if (it == _types.end()) { - throw vespalib::IllegalArgumentException( - "Allocation type not found: " + name, VESPA_STRLOC); - } - return *it->second; -} - -std::vector<const MemoryAllocationType*> -MemoryManager::getAllocationTypes() const -{ - vespalib::LockGuard lock(_typeLock); - std::vector<const MemoryAllocationType*> types; - for(std::map<std::string, MemoryAllocationType::UP>::const_iterator it - = _types.begin(); it != _types.end(); ++it) - { - types.push_back(it->second.get()); - } - return types; -} - -MemoryToken::UP -MemoryManager::allocate(const MemoryAllocationType& type, - uint64_t min, - uint64_t max, - uint8_t p, - ReduceMemoryUsageInterface* i) -{ - return _logic->allocate(type, min, max, p, i); -} - -uint64_t -MemoryManager::getMemorySizeFreeForPriority(uint8_t priority) const -{ - return _logic->getMemorySizeFreeForPriority(priority); -} - -void -MemoryManager::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - (void) verbose; (void) indent; - out << "Memory Manager {" << "\n" << indent << " "; - _logic->print(out, verbose, indent + " "); - out << "\n" << indent << "}"; -} - -} // defaultimplementation -} // framework -} // storage diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h deleted file mode 100644 index 86a2920095e..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorymanager.h +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::MemoryManager - * - * \brief Utility for tracking memory usage on distributor and storage nodes. - * - * The memory manager is responsible for limiting the memory allocated by - * various users within VDS storage and distributor nodes, such that these - * nodes don't use more memory than they can use, to avoid swapping. - * - * It will produce a status page to view big memory users, and give some - * historic data. It will track memory users and give them less and less memory - * as closer we are to utilizing all memory we are able to use. When getting - * close to full it will deny memory allocations to incoming commands that wants - * to use additional memory in able to complete the operation. - * - * The main class here defines the interface the client has to worry about. It - * should thus not point to the implementation in any way. - * - */ - -#pragma once - -#include <map> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> -#include <vespa/vespalib/util/printable.h> -#include <vespa/vespalib/util/sync.h> - -namespace storage::framework::defaultimplementation { - -class MemoryManager; -class AllocationLogic; -class MemoryState; - -class MemoryTokenImpl : public vespalib::Printable, - public MemoryToken -{ - friend class AllocationLogic; - - AllocationLogic& _logic; - ReduceMemoryUsageInterface* _reducer; - uint64_t _currentlyAllocated; - uint32_t _allocCount; - const MemoryAllocationType& _type; - uint8_t _priority; - -public: - typedef std::unique_ptr<MemoryTokenImpl> UP; - - MemoryTokenImpl(const MemoryTokenImpl &) = delete; - MemoryTokenImpl & operator = (const MemoryTokenImpl &) = delete; - MemoryTokenImpl(AllocationLogic& logic, - const MemoryAllocationType& type, - uint64_t allocated, - uint8_t priority, - ReduceMemoryUsageInterface* = 0); - - ~MemoryTokenImpl(); - - uint64_t getSize() const override { return _currentlyAllocated; } - uint64_t getAllocationCount() const { return _allocCount; } - const MemoryAllocationType& getType() const { return _type; } - ReduceMemoryUsageInterface* getReducer() const { return _reducer; } - - uint8_t getPriority() const { return _priority; } - - bool resize(uint64_t min, uint64_t max) override; - - void print(std::ostream& out, bool verbose, - const std::string& indent) const override; -}; - -class AllocationLogic : public vespalib::Printable -{ -protected: - /** - * MemoryTokens are friends with this class, such that logic classes - * can use this function to alter token size, without function for that - * being public. - */ - void setTokenSize(MemoryTokenImpl& token, uint64_t size) - { token._currentlyAllocated = size; } - - virtual MemoryToken::UP allocate(const MemoryAllocationType&, - uint8_t priority, - ReduceMemoryUsageInterface*) = 0; - virtual bool resize(MemoryToken& token, uint64_t min, uint64_t max, - uint32_t allocationCounts) = 0; -public: - typedef std::unique_ptr<AllocationLogic> UP; - virtual ~AllocationLogic() = 0; - - virtual void setMaximumMemoryUsage(uint64_t max) = 0; - - virtual void getState(MemoryState&, bool resetMax = false) = 0; - - /** - * Decide how much to allocate for this request. Should be between min - * and max, unless it's of a type that can be denied (such as external - * requests), in which case we can also deny allocation by returning a null - * token. - */ - MemoryToken::UP allocate(const MemoryAllocationType&, - uint64_t min, - uint64_t max, - uint8_t priority, - ReduceMemoryUsageInterface* = 0); - /** - * Resize the size in a token. If more memory is requested, then it might - * fail. The sizes given in min and max is given as total min and max, - * including any memory you may already have. If successful, the logic will - * have added this size to the token passed in. - */ - bool resize(MemoryTokenImpl& token, uint64_t min, uint64_t max); - - // Called by token destructor to free up tracked resources - virtual void freeToken(MemoryTokenImpl& token) = 0; - - virtual uint64_t getMemorySizeFreeForPriority(uint8_t priority) const = 0; - - // vespalib::Printable implementation - virtual void print(std::ostream& out, bool verbose, - const std::string& indent) const override = 0; -}; - -class MemoryManager : public vespalib::Printable, - public MemoryManagerInterface -{ - AllocationLogic::UP _logic; - vespalib::Lock _typeLock; - std::map<std::string, MemoryAllocationType::UP> _types; - -public: - typedef std::unique_ptr<MemoryManager> UP; - - MemoryManager(AllocationLogic::UP); - ~MemoryManager(); - - void setMaximumMemoryUsage(uint64_t max) override; - virtual void getState(MemoryState& state, bool resetMax = false); - - const MemoryAllocationType®isterAllocationType(const MemoryAllocationType& type) override; - const MemoryAllocationType&getAllocationType(const std::string& name) const override; - - std::vector<const MemoryAllocationType*> getAllocationTypes() const override; - - MemoryToken::UP allocate(const MemoryAllocationType&, uint64_t min, uint64_t max, - uint8_t p, ReduceMemoryUsageInterface* = 0) override; - - uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override; - - void print(std::ostream& out, bool verbose, const std::string& indent) const override; - -}; - -} diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp deleted file mode 100644 index 2351edd267c..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "memorystate.h" -#include <sstream> - -#include <vespa/log/log.h> -LOG_SETUP(".memory.state"); - -namespace storage::framework::defaultimplementation { - -MemoryState::Entry::Entry() - : _currentUsedSize(0), - _totalUserCount(0), - _currentUserCount(0), - _wantedCount(0), - _minimumCount(0), - _deniedCount(0), - _forcedBeyondMaximumCount(0) -{ -} - -void -MemoryState::Entry::operator+=(const Entry& other) -{ - _currentUsedSize += other._currentUsedSize; - _currentUserCount += other._currentUserCount; - _totalUserCount += other._totalUserCount; - _wantedCount += other._wantedCount; - _minimumCount += other._minimumCount; - _deniedCount += other._deniedCount; - _forcedBeyondMaximumCount += other._forcedBeyondMaximumCount; -} - -MemoryState::SnapShot& -MemoryState::SnapShot::operator+=(const MemoryState::SnapShot& other) -{ - for (AllocationMap::const_iterator it = other._allocations.begin(); - it != other._allocations.end(); ++it) - { - PriorityMap& map(_allocations[it->first]); - for (PriorityMap::const_iterator it2 = it->second.begin(); - it2 != it->second.end(); ++it2) - { - Entry& entry(map[it2->first]); - entry += it2->second; - } - } - return *this; -} - -uint64_t -MemoryState::SnapShot::getUserCount() const -{ - uint64_t count = 0; - for (AllocationMap::const_iterator it = _allocations.begin(); - it != _allocations.end(); ++it) - { - for (PriorityMap::const_iterator it2 = it->second.begin(); - it2 != it->second.end(); ++it2) - { - count += it2->second._currentUserCount; - } - } - return count; -} - -MemoryState::MemoryState(Clock& clock, uint64_t maxMemory) - : _clock(&clock), - _maxMemory(maxMemory), - _current(), - _max(), - _minJumpToUpdateMax(10 * 1024 * 1024) -{ -} - -MemoryState::MemoryState(const MemoryState &) = default; -MemoryState & MemoryState::operator = (const MemoryState &) = default; - -MemoryState::~MemoryState() {} - -void -MemoryState::addToEntry(const MemoryAllocationType& type, uint64_t memory, - uint8_t priority, - AllocationResult result, bool forcedAllocation, - uint64_t allocationCounts) -{ - LOG(spam, "Allocating memory %s - %lu bytes at priority %u. " - "Count %lu.", - type.getName().c_str(), memory, priority, allocationCounts); - PriorityMap& map(_current._allocations[&type]); - Entry& e(map[priority]); - e._currentUsedSize += memory; - e._totalUserCount += allocationCounts; - if (allocationCounts == 0) { - // Resizes adding no more users still count as another total - // allocation attempt. - ++e._totalUserCount; - } - e._currentUserCount += allocationCounts; - switch (result) { - case GOT_MAX: ++e._wantedCount; break; - case GOT_MIN: ++e._minimumCount; break; - case DENIED: ++e._deniedCount; break; - } - if (forcedAllocation) ++e._forcedBeyondMaximumCount; - _current._usedMemory += memory; - if (!type.isCache()) { - _current._usedWithoutCache += memory; - } - if (_current._usedWithoutCache - > _max._usedWithoutCache + _minJumpToUpdateMax) - { - LOG(spam, "Updating max to current %lu bytes of memory used", - _current._usedWithoutCache); - _max = _current; - _max._timeTaken = _clock->getTimeInSeconds(); - } -} - -void -MemoryState::removeFromEntry(const MemoryAllocationType& type, uint64_t memory, - uint8_t priority, - uint64_t allocationCounts) -{ - LOG(spam, "Freeing memory %s - %lu bytes at priority %u. " - "Count %lu.", - type.getName().c_str(), memory, priority, allocationCounts); - PriorityMap& map(_current._allocations[&type]); - Entry& e(map[priority]); - e._currentUsedSize -= memory; - e._currentUserCount -= allocationCounts; - _current._usedMemory -= memory; - if (!type.isCache()) { - _current._usedWithoutCache -= memory; - } -} - -void -MemoryState::Entry::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - (void) verbose; (void) indent; - std::ostringstream ost; - ost << "Used(" << _currentUsedSize << " B / " - << _currentUserCount << ") "; - for (uint32_t i=ost.str().size(); i<20; ++i) { - ost << " "; - } - - out << ost.str() - << "Stats(" << _totalUserCount - << ", " << _wantedCount - << ", " << _minimumCount - << ", " << _deniedCount - << ", " << _forcedBeyondMaximumCount << ")"; -} - -namespace { - void printAllocations(std::ostream& out, - const MemoryState::AllocationMap& map, - const std::string& indent) - { - std::map<std::string, std::string> allocs; - - for (MemoryState::AllocationMap::const_iterator it = map.begin(); - it != map.end(); ++it) - { - for (MemoryState::PriorityMap::const_iterator it2 - = it->second.begin(); it2 != it->second.end(); ++it2) - { - std::ostringstream name; - name << it->first->getName() << "(" - << static_cast<uint16_t>(it2->first) << "): "; - for (uint32_t i=name.str().size(); i<25; ++i) { - name << " "; - } - - std::ostringstream tmp; - it2->second.print(tmp, true, indent + " "); - - allocs[name.str()] = tmp.str(); - } - } - - for (std::map<std::string, std::string>::const_iterator it - = allocs.begin(); it != allocs.end(); ++it) - { - out << "\n" << indent << " " << it->first << it->second; - } - } -} - -void -MemoryState::SnapShot::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - out << "SnapShot(Used " << _usedMemory << ", w/o cache " - << _usedWithoutCache; - if (verbose) { - out << ") {"; - if (_usedMemory > 0) { - out << "\n" << indent << " Type(Pri): Used(Size/Allocs) " - << "Stats(Allocs, Wanted, Min, Denied, Forced)"; - } - printAllocations(out, _allocations, indent); - out << "\n" << indent << "}"; - } -} - -void -MemoryState::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - bool maxSet = (_max._usedWithoutCache > _current._usedWithoutCache); - out << "MemoryState(Max memory: " << _maxMemory << ") {" - << "\n" << indent << " Current: "; - _current.print(out, verbose, indent + " "); - if (maxSet) { - out << "\n" << indent << " Max: "; - _max.print(out, verbose, indent + " "); - } - out << "\n" << indent << "}"; -} - -} diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h deleted file mode 100644 index 3d670d43844..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/memorystate.h +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::MemoryState - * - * \brief Shows the state of current memory users - * - */ - -#pragma once - -#include "memorymanager.h" -#include <vespa/storageframework/generic/clock/clock.h> -#include <vespa/vespalib/util/sync.h> - -namespace storage::framework::defaultimplementation { - -class MemoryState : public vespalib::Printable { -public: - struct Entry { - // Total number of bytes allocated to this entry right now - uint64_t _currentUsedSize; - // Total number of allocations done on this entry - uint64_t _totalUserCount; - // Total number of allocations for this entry right now - uint32_t _currentUserCount; - // Amount of times this entry has gotten all the memory it wanted - uint32_t _wantedCount; - // Amount of times this entry has gotten less than all the memory - // it wanted - uint32_t _minimumCount; - // Amount of times this entry has been denied getting memory - uint32_t _deniedCount; - // Amount of times this entry has forced memory allocations beyond - // the maximum - uint32_t _forcedBeyondMaximumCount; - - Entry(); - - void print(std::ostream& out, bool verbose, - const std::string& indent) const; - - /** - * Set this instances counts to the counts from the other entry. - */ - void transferCounts(const Entry& other); - - void operator+=(const Entry& other); - }; - - typedef std::map<uint8_t, Entry> PriorityMap; - typedef std::map<const MemoryAllocationType*, PriorityMap> AllocationMap; - - /** - * A snapshot contains data for either current or max seen data. - * When a new maximum is seen, current is copied to max. - */ - class SnapShot : public vespalib::Printable { - friend class MemoryState; - - uint64_t _usedMemory; - uint64_t _usedWithoutCache; - SecondTime _timeTaken; - AllocationMap _allocations; - - public: - SnapShot() : vespalib::Printable() { clear(); } - SnapShot(const SnapShot& o) : vespalib::Printable() { (*this) = o; } - - void print(std::ostream& out, bool verbose, const std::string& indent) const override; - - void clear() { - _usedMemory = 0; - _usedWithoutCache = 0; - _timeTaken.setTime(0); - _allocations.clear(); - } - - SnapShot& operator=(const SnapShot& other) { - _usedMemory = other._usedMemory; - _usedWithoutCache = other._usedWithoutCache; - _timeTaken = other._timeTaken; - _allocations = other._allocations; - return *this; - } - - SnapShot& operator+=(const SnapShot& other); - - const AllocationMap& getAllocations() const { return _allocations; } - uint64_t getUsedSize() const { return _usedMemory; } - uint64_t getUsedSizeIgnoringCache() const { return _usedWithoutCache; } - uint64_t getUserCount() const; - }; - -private: - Clock* _clock; - uint64_t _maxMemory; - SnapShot _current; - SnapShot _max; - uint32_t _minJumpToUpdateMax; - -public: - MemoryState(Clock& clock, uint64_t maxMemory); - MemoryState(const MemoryState &); - MemoryState & operator = (const MemoryState &); - MemoryState(MemoryState &&) = default; - MemoryState & operator = (MemoryState &&) = default; - ~MemoryState(); - - void print(std::ostream& out, bool verbose, const std::string& indent) const override; - - void setMaximumMemoryUsage(uint64_t max) { _maxMemory = max; } - void setMinJumpToUpdateMax(uint32_t bytes) { _minJumpToUpdateMax = bytes; } - - enum AllocationResult { GOT_MAX, GOT_MIN, DENIED }; - void addToEntry(const MemoryAllocationType& type, uint64_t memory, - uint8_t priority, - AllocationResult result, bool forcedAllocation = false, - uint64_t allocationCounts = 1); - - void removeFromEntry(const MemoryAllocationType& type, uint64_t memory, - uint8_t priority, - uint64_t allocationCounts = 1); - void resetMax() { - _max = _current; - _max._timeTaken = _clock->getTimeInSeconds(); - } - - const SnapShot& getCurrentSnapshot() const { return _current; } - const SnapShot& getMaxSnapshot() const { return _max; } - - - uint64_t getTotalSize() const { return _maxMemory; } - uint64_t getFreeSize() const { - return _maxMemory > _current._usedMemory - ? _maxMemory - _current._usedMemory : 0; - } -}; - -} diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp deleted file mode 100644 index 16e03240a4f..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "nomemorymanager.h" -#include <vespa/vespalib/util/exceptions.h> - -namespace storage::framework::defaultimplementation { - -NoMemoryManager::~NoMemoryManager() {} - -const MemoryAllocationType& -NoMemoryManager::registerAllocationType(const MemoryAllocationType& type) -{ - vespalib::LockGuard lock(_typeLock); - _types[type.getName()] = MemoryAllocationType::UP( - new MemoryAllocationType(type)); - return *_types[type.getName()]; -} - -const MemoryAllocationType& -NoMemoryManager::getAllocationType(const std::string& name) const -{ - vespalib::LockGuard lock(_typeLock); - std::map<std::string, MemoryAllocationType::UP>::const_iterator it( - _types.find(name)); - if (it == _types.end()) { - throw vespalib::IllegalArgumentException( - "Allocation type not found: " + name, VESPA_STRLOC); - } - return *it->second; -} - -std::vector<const MemoryAllocationType*> -NoMemoryManager::getAllocationTypes() const -{ - vespalib::LockGuard lock(_typeLock); - std::vector<const MemoryAllocationType*> types; - for(std::map<std::string, MemoryAllocationType::UP>::const_iterator it - = _types.begin(); it != _types.end(); ++it) - { - types.push_back(it->second.get()); - } - return types; -} - -} - diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.h deleted file mode 100644 index 7f90b1360b8..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/nomemorymanager.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. -/** - * \class storage::NoMemoryManager - * - * \brief Memory manager that gives out max memory to everyone. - * - * Memory manager to use for testing and for apps not wanting to track memory. - * This manager will merely give out max to everyone who asks and not even keep - * track of anything. - */ - -#pragma once - -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> -#include <vespa/vespalib/util/printable.h> -#include <vespa/vespalib/util/sync.h> -#include <map> - -namespace storage { -namespace framework { -namespace defaultimplementation { - -class SimpleMemoryTokenImpl : public MemoryToken -{ - uint64_t _allocated; - -public: - SimpleMemoryTokenImpl(const SimpleMemoryTokenImpl &) = delete; - SimpleMemoryTokenImpl & operator = (const SimpleMemoryTokenImpl &) = delete; - SimpleMemoryTokenImpl(uint64_t allocated) : _allocated(allocated) {} - - uint64_t getSize() const override { return _allocated; } - bool resize(uint64_t /* min */, uint64_t max) override { _allocated = max; return true; } -}; - -class NoMemoryManager : public MemoryManagerInterface -{ - vespalib::Lock _typeLock; - std::map<std::string, MemoryAllocationType::UP> _types; - -public: - typedef std::unique_ptr<NoMemoryManager> UP; - - ~NoMemoryManager(); - - void setMaximumMemoryUsage(uint64_t) override {} - const MemoryAllocationType & registerAllocationType(const MemoryAllocationType& type) override; - const MemoryAllocationType & getAllocationType(const std::string& name) const override; - - MemoryToken::UP allocate(const MemoryAllocationType&, uint64_t /* min */, uint64_t max, - uint8_t /* priority */, ReduceMemoryUsageInterface* = 0) override - { - return SimpleMemoryTokenImpl::UP(new SimpleMemoryTokenImpl(max)); - } - uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override { - (void) priority; - return std::numeric_limits<uint64_t>().max(); - } - - std::vector<const MemoryAllocationType*> getAllocationTypes() const override; -}; - -} // defaultimplementation -} // framework -} // storage - diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp deleted file mode 100644 index 51bb7ba5019..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "prioritymemorylogic.h" - -#include <vespa/log/log.h> -LOG_SETUP(".memory.logic.priority"); - -namespace storage::framework::defaultimplementation { - -PriorityMemoryLogic::PriorityMemoryLogic(Clock& c, uint64_t maxMem) - : SimpleMemoryLogic(c, maxMem) -{ - LOG(debug, "Setup priority memory logic with max memory of %lu bytes", maxMem); -} - -float -PriorityMemoryLogic::getNonCacheThreshold(uint8_t priority) const -{ - return 0.6 + ((255 - priority) / 255.0) * 0.4; -} - -void -PriorityMemoryLogic::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - out << "PriorityMemoryLogic() : "; - SimpleMemoryLogic::print(out, verbose, indent); -} - -} diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h deleted file mode 100644 index 148c4f40aee..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/prioritymemorylogic.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -/** - * \class storage::PriorityMemoryLogic - * - * \brief Priority logic deciding who should get memory and how much. - * - */ - -#pragma once - -#include "simplememorylogic.h" -#include <vespa/vespalib/util/sync.h> - -namespace storage { -namespace framework { -namespace defaultimplementation { - -struct PriorityMemoryLogic : public SimpleMemoryLogic -{ - PriorityMemoryLogic(Clock&, uint64_t maxMemory); - float getNonCacheThreshold(uint8_t priority) const override; - void print(std::ostream& out, bool verbose, const std::string& indent) const override; -}; - -} // defaultimplementation -} // framework -} // storage - diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp b/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp deleted file mode 100644 index 76d6f990a00..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.cpp +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "simplememorylogic.h" -#include <vespa/vespalib/util/exceptions.h> - -#include <vespa/log/bufferedlogger.h> -LOG_SETUP(".memory.logic.simple"); - -namespace storage::framework::defaultimplementation { - -SimpleMemoryLogic::SimpleMemoryLogic(Clock& c, uint64_t maxMemory) - : _cacheThreshold(0.98), - _nonCacheThreshold(0.8), - _state(c, maxMemory), - _reducers() -{ - LOG(debug, "Setup simple memory logic with max memory of %" PRIu64 " bytes", maxMemory); -} - -SimpleMemoryLogic::~SimpleMemoryLogic() -{ -} - -void -SimpleMemoryLogic::setMaximumMemoryUsage(uint64_t max) -{ - vespalib::LockGuard lock(_stateLock); - _state.setMaximumMemoryUsage(max); -} - -void -SimpleMemoryLogic::getState(MemoryState& state, bool resetMax) { - vespalib::LockGuard lock(_stateLock); - state = _state; - if (resetMax) _state.resetMax(); -} - -MemoryToken::UP -SimpleMemoryLogic::allocate(const MemoryAllocationType& type, - uint8_t priority, - ReduceMemoryUsageInterface* reducer) -{ - MemoryTokenImpl::UP token( - new MemoryTokenImpl(*this, type, 0, priority, reducer)); - if (reducer != 0) { - vespalib::LockGuard lock(_stateLock); - _reducers.push_back(Reducer(*token, *reducer)); - } - return std::move(token); -} - -bool -SimpleMemoryLogic::resize(MemoryToken& tok, uint64_t min, uint64_t max, - uint32_t allocationCounts) -{ - vespalib::LockGuard lock(_stateLock); - MemoryTokenImpl& token(static_cast<MemoryTokenImpl&>(tok)); - LOG(spam, "Attempting to resize %s to size in the range %" PRIu64 " to " - "%" PRIu64 ".", token.toString().c_str(), min, max); - if (token.getSize() > max) { // Always safe to reduce size - handleReduction(token, max, allocationCounts); - return true; - } - // If not reducing size, calculate relative min/max values. - uint64_t relMin = (min > token.getSize() ? min - token.getSize() : 0); - uint64_t relMax = max - token.getSize(); - return resizeRelative(token, relMin, relMax, allocationCounts); -} - -void -SimpleMemoryLogic::handleReduction(MemoryTokenImpl& token, uint64_t max, - uint32_t allocationCounts) -{ - LOG(spam, "Reduzing size of token by %" PRIu64 ".", - token.getSize() - max); - _state.removeFromEntry(token.getType(), token.getSize() - max, - token.getPriority(), allocationCounts); - setTokenSize(token, max); -} - -bool -SimpleMemoryLogic::handleCacheMemoryRequest( - MemoryTokenImpl& token, uint64_t min, uint64_t max, - uint32_t allocationCounts) -{ - uint64_t usedSize(_state.getCurrentSnapshot().getUsedSize()); - uint64_t thresholdSize = uint64_t(getCacheThreshold() - * _state.getTotalSize()); - uint64_t toAllocate(thresholdSize > usedSize - ? std::min(thresholdSize - usedSize, max) - : 0); - bool forced = false; - if (token.getType().isAllocationsForced() && toAllocate < min) { - toAllocate = min; - forced = true; - } - if (toAllocate < min) { - LOG(spam, "We cannot give more memory to cache without going above " - "cache threshold (%" PRIu64 " B)", thresholdSize); - _state.addToEntry(token.getType(), 0, token.getPriority(), - MemoryState::DENIED, false, allocationCounts); - return false; - } - LOG(spam, "Giving %" PRIu64 " bytes of memory to cache. (Cache threshold " - "is %" PRIu64 ", used size is %" PRIu64 ", %" PRIu64 " bytes were " - "always allocated to the token and it wanted memory between %" - PRIu64 " and %" PRIu64 ".", - toAllocate, thresholdSize, usedSize, token.getSize(), min, max); - _state.addToEntry(token.getType(), toAllocate, token.getPriority(), - static_cast<uint64_t>(toAllocate) >= max - ? MemoryState::GOT_MAX : MemoryState::GOT_MIN, - forced, allocationCounts); - setTokenSize(token, token.getSize() + toAllocate); - return true; -} - -uint64_t -SimpleMemoryLogic::getMemorySizeFreeForPriority(uint8_t priority) const -{ - uint64_t usedSize(_state.getCurrentSnapshot().getUsedSizeIgnoringCache()); - uint64_t thresholdSize = uint64_t(getNonCacheThreshold(priority) - * _state.getTotalSize()); - return (usedSize >= thresholdSize ? 0 : thresholdSize - usedSize); -} - -bool -SimpleMemoryLogic::resizeRelative( - MemoryTokenImpl& token, uint64_t min, uint64_t max, - uint32_t allocationCounts) -{ - LOG(spam, "Relative resize change. Need another %zu-%zu byte of memory.", - min, max); - // If requester is cache, use cache threshold - if (token.getType().isCache()) { - return handleCacheMemoryRequest(token, min, max, allocationCounts); - } - // If we get here, requester is not cache. - uint64_t usedSize(_state.getCurrentSnapshot().getUsedSizeIgnoringCache()); - uint64_t thresholdSize = uint64_t(getNonCacheThreshold(token.getPriority()) - * _state.getTotalSize()); - uint64_t toAllocate = 0; - if (thresholdSize > usedSize) { - toAllocate = std::min(max, thresholdSize - usedSize); - } - if (toAllocate < min) toAllocate = min; - bool forced = false; - if (usedSize + toAllocate > _state.getTotalSize()) { - if (token.getType().isAllocationsForced()) { - forced = true; - } else { - LOG(spam, "We cannot give more memory without going beyond max"); - _state.addToEntry(token.getType(), 0, token.getPriority(), - MemoryState::DENIED, false, allocationCounts); - return false; - } - } - // External load should not fill up too much - if (usedSize + toAllocate > thresholdSize - && token.getType().isExternalLoad() - && !token.getType().isAllocationsForced()) - { - LOG(spam, "Not giving external load memory beyond threshold."); - _state.addToEntry(token.getType(), 0, token.getPriority(), - MemoryState::DENIED, false, allocationCounts); - return false; - } - // If this puts us above max with cache, remove some cache. - if (_state.getCurrentSnapshot().getUsedSize() + toAllocate - > _state.getTotalSize()) - { - uint64_t needed(_state.getCurrentSnapshot().getUsedSize() - + toAllocate - _state.getTotalSize()); - for (uint32_t i=0; i<_reducers.size(); ++i) { - MemoryTokenImpl& rtoken(*_reducers[i]._token); - uint64_t reduceBy(std::min(needed, rtoken.getSize())); - uint64_t reduced(_reducers[i]._reducer->reduceMemoryConsumption( - rtoken, reduceBy)); - _state.removeFromEntry(rtoken.getType(), reduced, - rtoken.getPriority(), 0); - setTokenSize(rtoken, rtoken.getSize() - reduced); - needed -= reduceBy; - if (needed == 0) break; - if (reduced < reduceBy) { - LOG(debug, "Reducer refused to free the full %" PRIu64 " bytes " - "requested. %" PRIu64 " bytes reduced in token %s.", - reduceBy, reduced, rtoken.toString().c_str()); - } - } - } - if (_state.getCurrentSnapshot().getUsedSize() + toAllocate - > _state.getTotalSize()) - { - LOGBP(debug, "Failed to free enough memory from cache. This puts us " - "above max memory."); - } - LOG(spam, "Giving %" PRIu64 " bytes of memory", toAllocate); - _state.addToEntry(token.getType(), toAllocate, token.getPriority(), - static_cast<uint64_t>(toAllocate) >= max - ? MemoryState::GOT_MAX : MemoryState::GOT_MIN, - forced, allocationCounts); - setTokenSize(token, token.getSize() + toAllocate); - return true; -} - -void -SimpleMemoryLogic::freeToken(MemoryTokenImpl& token) -{ - vespalib::LockGuard lock(_stateLock); - _state.removeFromEntry(token.getType(), token.getSize(), - token.getPriority(), token.getAllocationCount()); - if (token.getReducer() != 0) { - std::vector<Reducer> reducers; - reducers.reserve(_reducers.size() - 1); - for (uint32_t i=0; i<_reducers.size(); ++i) { - if (_reducers[i]._token != &token) reducers.push_back(_reducers[i]); - } - assert(reducers.size() + 1 == _reducers.size()); - reducers.swap(_reducers); - } -} - -void -SimpleMemoryLogic::print(std::ostream& out, bool verbose, - const std::string& indent) const -{ - out << "SimpleMemoryLogic() {\n" - << indent << " "; - vespalib::LockGuard lock(_stateLock); - _state.print(out, verbose, indent + " "); -} - -} diff --git a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h b/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h deleted file mode 100644 index c394db40b29..00000000000 --- a/storageframework/src/vespa/storageframework/defaultimplementation/memory/simplememorylogic.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -/** - * \class storage::SimpleMemoryLogic - * - * \brief Simple logic deciding who should get memory and how much. - * - * There is a cache threshold. By default 98%. Cache will always get memory up - * till this fillrate. - * - * There is a non-cache threshold. Non-cache memory requesters will get maximum - * memory until threshold is reached. If getting maximum memory would go beyond - * the non-cache threshold, the requester will get enough memory to hit the - * threshold (if more than minimum), or get the minimum memory asked for, if - * that doesn't put usage above 100%. - * - * Usage above 100% is attempted avoided by freeing cache memory. If failing to - * free enough memory, request will fail, or minimum will be get if allocation - * is forced such that it cannot fail. In such a case, usage may go beyond 100%. - */ - -#pragma once - -#include "memorymanager.h" -#include "memorystate.h" -#include <vespa/vespalib/util/sync.h> - -namespace storage { -namespace framework { -namespace defaultimplementation { - -class SimpleMemoryLogic : public AllocationLogic -{ - float _cacheThreshold; - float _nonCacheThreshold; - vespalib::Lock _stateLock; - MemoryState _state; - struct Reducer { - MemoryTokenImpl* _token; - ReduceMemoryUsageInterface* _reducer; - - Reducer() : _token(0), _reducer(0) {} - Reducer(MemoryTokenImpl& t, - ReduceMemoryUsageInterface& r) - : _token(&t), _reducer(&r) {} - }; - std::vector<Reducer> _reducers; - -protected: - float getCacheThreshold() { return _cacheThreshold; } - - // Priority memory logic can override this to set a threshold based on - // priority - virtual float getNonCacheThreshold(uint8_t priority) const - { (void) priority; return _nonCacheThreshold; } - -public: - typedef std::unique_ptr<SimpleMemoryLogic> UP; - - SimpleMemoryLogic(Clock&, uint64_t maxMemory); - - ~SimpleMemoryLogic(); - - SimpleMemoryLogic& setMinJumpToUpdateMax(uint32_t bytes) { - _state.setMinJumpToUpdateMax(bytes); - return *this; - } - - void setMaximumMemoryUsage(uint64_t max) override; - - void setCacheThreshold(float limit) { _cacheThreshold = limit; } - void setNonCacheThreshold(float limit) { _nonCacheThreshold = limit; } - - MemoryState& getState() { return _state; } // Not threadsafe. Unit testing. - void getState(MemoryState& state, bool resetMax) override; - - MemoryToken::UP allocate(const MemoryAllocationType&, uint8_t priority, - ReduceMemoryUsageInterface* = 0) override; - bool resize(MemoryToken& token, uint64_t min, uint64_t max, uint32_t allocationCounts) override; - - void freeToken(MemoryTokenImpl& token) override; - void print(std::ostream& out, bool verbose, const std::string& indent) const override; - - virtual uint64_t getMemorySizeFreeForPriority(uint8_t priority) const override; - -private: - void handleReduction(MemoryTokenImpl&, uint64_t size, - uint32_t allocationCounts); - bool resizeRelative(MemoryTokenImpl&, uint64_t min, uint64_t max, - uint32_t allocationCounts); - bool handleCacheMemoryRequest(MemoryTokenImpl&, uint64_t min, uint64_t max, - uint32_t allocationCounts); -}; - -} // defaultimplementation -} // framework -} // storage - diff --git a/storageframework/src/vespa/storageframework/generic/CMakeLists.txt b/storageframework/src/vespa/storageframework/generic/CMakeLists.txt index cf225f8d5aa..5df9963304d 100644 --- a/storageframework/src/vespa/storageframework/generic/CMakeLists.txt +++ b/storageframework/src/vespa/storageframework/generic/CMakeLists.txt @@ -4,7 +4,6 @@ vespa_add_library(storageframework_generic $<TARGET_OBJECTS:storageframework_component> $<TARGET_OBJECTS:storageframework_status> $<TARGET_OBJECTS:storageframework_thread> - $<TARGET_OBJECTS:storageframework_memory> $<TARGET_OBJECTS:storageframework_clock> INSTALL lib64 DEPENDS diff --git a/storageframework/src/vespa/storageframework/generic/memory/.gitignore b/storageframework/src/vespa/storageframework/generic/memory/.gitignore deleted file mode 100644 index 7e7c0fe7fae..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/.depend -/Makefile diff --git a/storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt b/storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt deleted file mode 100644 index 77aa14e4ca2..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(storageframework_memory OBJECT - SOURCES - memorytoken.cpp - DEPENDS -) diff --git a/storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h b/storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h deleted file mode 100644 index e91b7946454..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/memoryallocationtype.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::framework::MemoryAllocationType - * \ingroup memory - * - * \brief Allocation types used to differ between memory manager clients. - * - * The different memory manager clients have different properties. It is - * important for the memory manager to distinguish between different users in - * order to know how to prioritize memory, and also in order to create good - * reports on memory usage. - * - * An allocation type holds metadata for a memory manager client, including a - * name for the type and various properties that may affect how much memory - * such a client will get, whether it always gets some, etc. - */ - -#pragma once - -#include <string> -#include <memory> - -namespace storage::framework { - -struct MemoryAllocationType { - using UP = std::unique_ptr<MemoryAllocationType>; - - enum Flags { - NONE = 0x00, - FORCE_ALLOCATE = 0x01, - EXTERNAL_LOAD = 0x02, - CACHE = 0x04 - }; - - MemoryAllocationType() - : _flags(NONE), _name("") {}; - - MemoryAllocationType(const std::string& name, uint32_t flags = NONE) - : _flags(flags), _name(name) {} - - const std::string& getName() const { return _name; } - bool isAllocationsForced() const { return (_flags & FORCE_ALLOCATE); } - bool isExternalLoad() const { return (_flags & EXTERNAL_LOAD); } - bool isCache() const { return (_flags & CACHE); } - -private: - uint32_t _flags; - std::string _name; -}; - -} diff --git a/storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h b/storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h deleted file mode 100644 index a97038c2aab..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/memorymanagerinterface.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::framework::MemoryManagerInterface - * \ingroup memory - * - * \brief Interface with functions clients need in order to use a memory manager - * - * This interface exist so clients can use a memory manager without actually - * depending on the implementation of it. - */ - -#pragma once - -#include "memoryallocationtype.h" -#include "memorytoken.h" -#include "reducememoryusageinterface.h" -#include <vector> - -namespace storage::framework { - -struct MemoryManagerInterface -{ - typedef std::unique_ptr<MemoryManagerInterface> UP; - - virtual ~MemoryManagerInterface() {} - - virtual void setMaximumMemoryUsage(uint64_t max) = 0; - - /** - * Registers the given allocation type by copying it, and returning - * a reference to the copied object. - */ - virtual const MemoryAllocationType& - registerAllocationType(const MemoryAllocationType& type) = 0; - - /** Throws exception if failing to find type. */ - virtual const MemoryAllocationType& - getAllocationType(const std::string& name) const = 0; - - /** Get an overview of all registration types. */ - virtual std::vector<const MemoryAllocationType*> - getAllocationTypes() const = 0; - - /** - * Decide how much to allocate for this request. Should be between min - * and max, unless it's of a type that can be denied (such as external - * requests), in which case we can also deny allocation by returning a null - * token. - */ - virtual MemoryToken::UP allocate( - const MemoryAllocationType&, - uint64_t min, - uint64_t max, - uint8_t priority, - ReduceMemoryUsageInterface* = 0) = 0; - - /** - * Utility function to see how much memory is available. - */ - virtual uint64_t getMemorySizeFreeForPriority(uint8_t priority) const = 0; -}; - -} diff --git a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp b/storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp deleted file mode 100644 index 3950810a010..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "memorytoken.h" - -namespace storage { -namespace framework { - -MemoryToken::~MemoryToken() -{ -} - -} // framework -} // storage diff --git a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.h b/storageframework/src/vespa/storageframework/generic/memory/memorytoken.h deleted file mode 100644 index cd389d64549..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/memorytoken.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::framework::MemoryToken - * \ingroup memory - * - * \brief Token to keep by client for current allocations. - * - * This class is a token a memory manager client will get from the memory - * manager when getting memory. It can be used to know how much you currently - * have allocated, and through it you can request more or less memory. - */ - -#pragma once - -#include <memory> - -namespace storage::framework { - -class MemoryToken { -protected: -public: - typedef std::unique_ptr<MemoryToken> UP; - virtual ~MemoryToken(); - - virtual uint64_t getSize() const = 0; - virtual bool resize(uint64_t min, uint64_t max) = 0; -}; - -} diff --git a/storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h b/storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h deleted file mode 100644 index cd50d33015c..00000000000 --- a/storageframework/src/vespa/storageframework/generic/memory/reducememoryusageinterface.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class storage::framework::ReduceMemoryUsageInterface - * \ingroup memory - * - * \brief The manager can take memory back when needed using this interface. - * - * Some memory users, typically caches, wants to use all available memory. But - * to let them use all available memory, it must also be easy to take memory - * back when needed for something else. An implementation of this interface can - * be given on memory allocations to give the memory manager the ability to take - * memory back when needed. - */ - -#pragma once - -namespace storage::framework { - -struct ReduceMemoryUsageInterface -{ - virtual ~ReduceMemoryUsageInterface() {} - - /** - * This callback is called when the memory manager want to reduce the usage - * of the given memory token. Actual memory to be released should be - * released in this function. The token itself will be adjusted by the - * memory manager though. The memory manager may keep a lock through this - * call, so no memory manager calls should be made inside this callback. - * - * It is recommended that you actually release at least as many bytes as - * requested. Though currently it is allowed to reduce less or refuse, but - * this might mean that some higher priority task does not get the memory it - * needs. - * - * @param reduceBy Always in the range 0 < reduceBy <= token.size() - * @return The amount of memory no longer used. - */ - virtual uint64_t reduceMemoryConsumption(const MemoryToken&, uint64_t reduceBy) = 0; -}; - -} diff --git a/storageframework/src/vespa/storageframework/generic/thread/thread.cpp b/storageframework/src/vespa/storageframework/generic/thread/thread.cpp index 1a88b4d2044..5ed3f7dc5e6 100644 --- a/storageframework/src/vespa/storageframework/generic/thread/thread.cpp +++ b/storageframework/src/vespa/storageframework/generic/thread/thread.cpp @@ -17,5 +17,16 @@ Thread::interruptAndJoin(vespalib::Monitor* m) join(); } +void +Thread::interruptAndJoin(std::mutex &m, std::condition_variable &cv) +{ + interrupt(); + { + std::lock_guard<std::mutex> guard(m); + cv.notify_all(); + } + join(); +} + } // framework } // storage diff --git a/storageframework/src/vespa/storageframework/generic/thread/thread.h b/storageframework/src/vespa/storageframework/generic/thread/thread.h index ceeba79ebe2..72054ff725a 100644 --- a/storageframework/src/vespa/storageframework/generic/thread/thread.h +++ b/storageframework/src/vespa/storageframework/generic/thread/thread.h @@ -14,6 +14,8 @@ #include "runnable.h" #include <vespa/vespalib/stllike/string.h> +#include <mutex> +#include <condition_variable> namespace vespalib { class Monitor; @@ -58,6 +60,8 @@ public: * through a monitor after the signalling face. */ void interruptAndJoin(vespalib::Monitor* m); + + void interruptAndJoin(std::mutex &m, std::condition_variable &cv); }; } diff --git a/storageframework/src/vespa/storageframework/storageframework.h b/storageframework/src/vespa/storageframework/storageframework.h index f9847d5fd12..4ab36fa099f 100644 --- a/storageframework/src/vespa/storageframework/storageframework.h +++ b/storageframework/src/vespa/storageframework/storageframework.h @@ -7,7 +7,6 @@ #include <vespa/storageframework/generic/clock/clock.h> #include <vespa/storageframework/generic/clock/timer.h> #include <vespa/storageframework/generic/component/component.h> -#include <vespa/storageframework/generic/memory/memorymanagerinterface.h> #include <vespa/storageframework/generic/metric/metricupdatehook.h> #include <vespa/storageframework/generic/status/htmlstatusreporter.h> #include <vespa/storageframework/generic/status/statusreportermap.h> diff --git a/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp b/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp index 64c2c9a6429..7ebb71f1855 100644 --- a/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp +++ b/streamingvisitors/src/vespa/searchvisitor/searchenvironment.cpp @@ -15,9 +15,7 @@ __thread SearchEnvironment::EnvMap * SearchEnvironment::_localEnvMap=0; SearchEnvironment::Env::Env(const vespalib::string & muffens, const config::ConfigUri & configUri, Fast_NormalizeWordFolder & wf) : _configId(configUri.getConfigId()), - _configurer(config::SimpleConfigRetriever::UP( - new config::SimpleConfigRetriever(createKeySet(configUri.getConfigId()), configUri.getContext())), - this), + _configurer(std::make_unique<config::SimpleConfigRetriever>(createKeySet(configUri.getConfigId()), configUri.getContext()), this), _vsmAdapter(new VSMAdapter(muffens, _configId, wf)), _rankManager(new RankManager(_vsmAdapter.get())) { @@ -63,13 +61,15 @@ SearchEnvironment::~SearchEnvironment() _threadLocals.clear(); } -SearchEnvironment::Env & SearchEnvironment::getEnv(const vespalib::string & searchCluster) +SearchEnvironment::Env & +SearchEnvironment::getEnv(const vespalib::string & searchCluster) { config::ConfigUri searchClusterUri(_configUri.createWithNewId(searchCluster)); if (_localEnvMap == NULL) { - _localEnvMap = new EnvMap; + EnvMapUP envMap = std::make_unique<EnvMap>(); + _localEnvMap = envMap.get(); vespalib::LockGuard guard(_lock); - _threadLocals.push_back(EnvMapUP(_localEnvMap)); + _threadLocals.emplace_back(std::move(envMap)); } EnvMap::iterator localFound = _localEnvMap->find(searchCluster); if (localFound == _localEnvMap->end()) { @@ -77,7 +77,8 @@ SearchEnvironment::Env & SearchEnvironment::getEnv(const vespalib::string & sear EnvMap::iterator found = _envMap.find(searchCluster); if (found == _envMap.end()) { LOG(debug, "Init VSMAdapter with config id = '%s'", searchCluster.c_str()); - _envMap[searchCluster].reset(new Env("*", searchClusterUri, _wordFolder)); + Env::SP env = std::make_shared<Env>("*", searchClusterUri, _wordFolder); + _envMap[searchCluster] = std::move(env); found = _envMap.find(searchCluster); } _localEnvMap->insert(*found); diff --git a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp index 51c01a40fac..e3483e8beac 100644 --- a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp +++ b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp @@ -31,6 +31,8 @@ using search::attribute::IAttributeVector; using search::aggregation::HitsAggregationResult; using search::expression::ConfigureStaticParams; using vdslib::Parameters; +using document::PositionDataType; +using document::DataType; class ForceWordfolderInit { @@ -52,25 +54,25 @@ static ForceWordfolderInit _G_forceNormWordFolderInit; AttributeVector::SP createMultiValueAttribute(const vespalib::string & name, const document::FieldValue & fv, bool arrayType) { - const document::DataType * ndt = fv.getDataType(); + const DataType * ndt = fv.getDataType(); if (ndt->inherits(document::CollectionDataType::classId)) { ndt = &(static_cast<const document::CollectionDataType *>(ndt))->getNestedType(); } LOG(debug, "Create %s attribute '%s' with data type '%s' (%s)", arrayType ? "array" : "weighted set", name.c_str(), ndt->getName().c_str(), fv.getClass().name()); AttributeVector::SP attr; - if (ndt->getId() == document::DataType::T_BYTE || - ndt->getId() == document::DataType::T_INT || - ndt->getId() == document::DataType::T_LONG) + if (ndt->getId() == DataType::T_BYTE || + ndt->getId() == DataType::T_INT || + ndt->getId() == DataType::T_LONG) { attr.reset(arrayType ? static_cast<AttributeVector *>(new search::MultiIntegerExtAttribute(name)) : static_cast<AttributeVector *>(new search::WeightedSetIntegerExtAttribute(name))); - } else if (ndt->getId() == document::DataType::T_DOUBLE || - ndt->getId() == document::DataType::T_FLOAT) + } else if (ndt->getId() == DataType::T_DOUBLE || + ndt->getId() == DataType::T_FLOAT) { attr.reset(arrayType ? static_cast<AttributeVector *>(new search::MultiFloatExtAttribute(name)) : static_cast<AttributeVector *>(new search::WeightedSetFloatExtAttribute(name))); - } else if (ndt->getId() == document::DataType::T_STRING) { + } else if (ndt->getId() == DataType::T_STRING) { attr.reset(arrayType ? static_cast<AttributeVector *>(new search::MultiStringExtAttribute(name)) : static_cast<AttributeVector *>(new search::WeightedSetStringExtAttribute(name))); } else { @@ -204,27 +206,26 @@ void SearchVisitor::init(const Parameters & params) _attrMan.add(_rankAttributeBacking); Parameters::ValueRef valueRef; if ( params.get("summaryclass", valueRef) ) { - _summaryClass = vespalib::string(static_cast<const char *>(valueRef.data()), - static_cast<unsigned>(valueRef.size())); + _summaryClass = vespalib::string(valueRef.data(), valueRef.size()); LOG(debug, "Received summary class: %s", _summaryClass.c_str()); } size_t wantedSummaryCount(10); if (params.get("summarycount", valueRef) ) { - vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size()); + vespalib::string tmp(valueRef.data(), valueRef.size()); wantedSummaryCount = strtoul(tmp.c_str(), NULL, 0); LOG(debug, "Received summary count: %ld", wantedSummaryCount); } _queryResult->getSearchResult().setWantedHitCount(wantedSummaryCount); if (params.get("rankprofile", valueRef) ) { - vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size()); + vespalib::string tmp(valueRef.data(), valueRef.size()); _rankController.setRankProfile(tmp); LOG(debug, "Received rank profile: %s", _rankController.getRankProfile().c_str()); } if (params.get("queryflags", valueRef) ) { - vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size()); + vespalib::string tmp(valueRef.data(), valueRef.size()); LOG(debug, "Received query flags: 0x%lx", strtoul(tmp.c_str(), NULL, 0)); uint32_t queryFlags = strtoul(tmp.c_str(), NULL, 0); _rankController.setDumpFeatures((queryFlags & search::fs4transport::QFLAG_DUMP_FEATURES) != 0); @@ -234,7 +235,7 @@ void SearchVisitor::init(const Parameters & params) if (params.get("rankproperties", valueRef) && valueRef.size() > 0) { LOG(spam, "Received rank properties of %zd bytes", valueRef.size()); uint32_t len = static_cast<uint32_t>(valueRef.size()); - char * data = const_cast<char *>(static_cast<const char *>(valueRef.data())); + char * data = const_cast<char *>(valueRef.data()); FNET_DataBuffer src(data, len); uint32_t cnt = src.ReadInt32(); len -= sizeof(uint32_t); @@ -259,7 +260,7 @@ void SearchVisitor::init(const Parameters & params) } if (params.get("rankprofile", valueRef)) { - vespalib::string tmp(static_cast<const char *>(valueRef.data()), valueRef.size()); + vespalib::string tmp(valueRef.data(), valueRef.size()); _summaryGenerator.getDocsumState()._args.SetRankProfile(tmp); } @@ -270,7 +271,7 @@ void SearchVisitor::init(const Parameters & params) vespalib::string location; if (params.get("location", valueRef)) { - location = vespalib::string(static_cast<const char *>(valueRef.data()), valueRef.size()); + location = vespalib::string(valueRef.data(), valueRef.size()); LOG(debug, "Location = '%s'", location.c_str()); _summaryGenerator.getDocsumState()._args.SetLocation(valueRef.size(), (const char*)valueRef.data()); } @@ -278,14 +279,12 @@ void SearchVisitor::init(const Parameters & params) Parameters::ValueRef searchClusterBlob; if (params.get("searchcluster", searchClusterBlob)) { LOG(spam, "Received searchcluster blob of %zd bytes", searchClusterBlob.size()); - vespalib::string searchCluster(static_cast<const char *>(searchClusterBlob.data()), searchClusterBlob.size()); + vespalib::string searchCluster(searchClusterBlob.data(), searchClusterBlob.size()); _vsmAdapter = _env.getVSMAdapter(searchCluster); if ( params.get("sort", valueRef) ) { search::uca::UcaConverterFactory ucaFactory; - _sortSpec = search::common::SortSpec(vespalib::string(static_cast<const char *>(valueRef.data()), - static_cast<unsigned>(valueRef.size())), - ucaFactory); + _sortSpec = search::common::SortSpec(vespalib::string(valueRef.data(), valueRef.size()), ucaFactory); LOG(debug, "Received sort specification: '%s'", _sortSpec.getSpec().c_str()); } @@ -388,16 +387,16 @@ SearchVisitor::AttributeInserter::onPrimitive(uint32_t, const Content & c) } } -SearchVisitor::AttributeInserter::AttributeInserter(search::AttributeVector & attribute, search::AttributeVector::DocId docId) : +SearchVisitor::AttributeInserter::AttributeInserter(AttributeVector & attribute, AttributeVector::DocId docId) : _attribute(attribute), _docId(docId) { } -SearchVisitor::PositionInserter::PositionInserter(search::AttributeVector & attribute, search::AttributeVector::DocId docId) : +SearchVisitor::PositionInserter::PositionInserter(AttributeVector & attribute, AttributeVector::DocId docId) : AttributeInserter(attribute, docId), - _fieldX(document::PositionDataType::getInstance().getField(document::PositionDataType::FIELD_X)), - _fieldY(document::PositionDataType::getInstance().getField(document::PositionDataType::FIELD_Y)) + _fieldX(PositionDataType::getInstance().getField(PositionDataType::FIELD_X)), + _fieldY(PositionDataType::getInstance().getField(PositionDataType::FIELD_Y)) { } @@ -605,7 +604,7 @@ void SearchVisitor::SyntheticFieldsController::onDocumentMatch(StorageDocument & document, const vespalib::string & documentId) { - document.setField(_documentIdFId, document::FieldValue::UP(new document::StringFieldValue(documentId))); + document.setField(_documentIdFId, std::make_unique<document::StringFieldValue>(documentId)); } void @@ -617,8 +616,8 @@ SearchVisitor::registerAdditionalFields(const std::vector<vsm::DocsumTools::Fiel const std::vector<vespalib::string> & inputNames = spec.getInputNames(); for (size_t j = 0; j < inputNames.size(); ++j) { fieldList.push_back(inputNames[j]); - if (document::PositionDataType::isZCurveFieldName(inputNames[j])) { - fieldList.push_back(document::PositionDataType::cutZCurveFieldName(inputNames[j])); + if (PositionDataType::isZCurveFieldName(inputNames[j])) { + fieldList.push_back(PositionDataType::cutZCurveFieldName(inputNames[j])); } } } @@ -991,16 +990,16 @@ SearchVisitor::fillAttributeVectors(const vespalib::string & documentId, const S { for (const AttrInfo & finfo : _attributeFields) { const AttributeGuard &finfoGuard(*finfo._attr); - bool isPosition = finfoGuard->getClass().inherits(search::IntegerAttribute::classId) && document::PositionDataType::isZCurveFieldName(finfoGuard->getName()); + bool isPosition = finfoGuard->getClass().inherits(search::IntegerAttribute::classId) && PositionDataType::isZCurveFieldName(finfoGuard->getName()); LOG(debug, "Filling attribute '%s', isPosition='%s'", finfoGuard->getName().c_str(), isPosition ? "true" : "false"); uint32_t fieldId = finfo._field; if (isPosition) { - vespalib::stringref org = document::PositionDataType::cutZCurveFieldName(finfoGuard->getName()); + vespalib::stringref org = PositionDataType::cutZCurveFieldName(finfoGuard->getName()); fieldId = _fieldsUnion.find(org)->second; } const StorageDocument::SubDocument & subDoc = document.getComplexField(fieldId); - search::AttributeVector & attrV = const_cast<search::AttributeVector & >(*finfoGuard); - search::AttributeVector::DocId docId(0); + AttributeVector & attrV = const_cast<AttributeVector & >(*finfoGuard); + AttributeVector::DocId docId(0); attrV.addDoc(docId); if (subDoc.getFieldValue() != NULL) { LOG(debug, "value = '%s'", subDoc.getFieldValue()->toString().c_str()); diff --git a/testutil/pom.xml b/testutil/pom.xml index 9f98f40c2d0..00f606860a4 100644 --- a/testutil/pom.xml +++ b/testutil/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>testutil</artifactId> <packaging>jar</packaging> @@ -23,6 +24,7 @@ <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <scope>provided</scope> + <classifier>no_aop</classifier> </dependency> <dependency> <groupId>org.hamcrest</groupId> diff --git a/travis/travis-build-full.sh b/travis/travis-build-full.sh index fc0efb843aa..53e174b534b 100755 --- a/travis/travis-build-full.sh +++ b/travis/travis-build-full.sh @@ -6,7 +6,7 @@ export SOURCE_DIR=/source export NUM_THREADS=6 export MALLOC_ARENA_MAX=1 export MAVEN_OPTS="-Xms128m -Xmx2g" -source /etc/profile.d/devtoolset-6.sh || true +source /etc/profile.d/devtoolset-7.sh || true ccache --max-size=1250M ccache --set-config=compression=true diff --git a/vbench/src/vbench/core/dispatcher.h b/vbench/src/vbench/core/dispatcher.h index 6336df17965..212cf04a06e 100644 --- a/vbench/src/vbench/core/dispatcher.h +++ b/vbench/src/vbench/core/dispatcher.h @@ -6,6 +6,7 @@ #include "provider.h" #include "closeable.h" #include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <vector> namespace vbench { diff --git a/vdslib/pom.xml b/vdslib/pom.xml index 48681b8ce83..5063489ed66 100644 --- a/vdslib/pom.xml +++ b/vdslib/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vdslib</artifactId> <packaging>container-plugin</packaging> diff --git a/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt b/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt index 86406937dd2..44693b42fe8 100644 --- a/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt +++ b/vdslib/src/vespa/vdslib/distribution/CMakeLists.txt @@ -2,6 +2,7 @@ vespa_add_library(vdslib_distribution OBJECT SOURCES distribution.cpp + distribution_config_util.cpp group.cpp idealnodecalculatorimpl.cpp redundancygroupdistribution.cpp diff --git a/vdslib/src/vespa/vdslib/distribution/distribution.cpp b/vdslib/src/vespa/vdslib/distribution/distribution.cpp index 908de49e311..ff84687210d 100644 --- a/vdslib/src/vespa/vdslib/distribution/distribution.cpp +++ b/vdslib/src/vespa/vdslib/distribution/distribution.cpp @@ -1,18 +1,19 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "distribution.h" +#include <vespa/vdslib/distribution/distribution_config_util.h> #include <vespa/vdslib/state/clusterstate.h> #include <vespa/vdslib/state/random.h> -#include <vespa/vespalib/text/stringtokenizer.h> #include <vespa/vespalib/util/bobhash.h> #include <vespa/vespalib/stllike/asciistream.h> -#include <boost/lexical_cast.hpp> #include <vespa/config/config.h> #include <vespa/config/print/asciiconfigwriter.h> #include <vespa/config/print/asciiconfigreader.h> #include <vespa/vespalib/util/exceptions.h> #include <vespa/config-stor-distribution.h> #include <list> +#include <algorithm> +#include <cmath> #include <vespa/log/bufferedlogger.h> LOG_SETUP(".vdslib.distribution"); @@ -115,17 +116,6 @@ Distribution::operator=(const Distribution& d) Distribution::~Distribution() { } namespace { - std::vector<uint16_t> getGroupPath(const vespalib::stringref & path) { - vespalib::StringTokenizer st(path, ".", ""); - std::vector<uint16_t> result(st.size()); - for (uint32_t i=0, n=result.size(); i<n; ++i) { - result[i] = boost::lexical_cast<uint16_t>(st[i]); - } - return result; - } -} - -namespace { using ConfigDiskDistribution = vespa::config::content::StorDistributionConfig::DiskDistribution; Distribution::DiskDistribution fromConfig(ConfigDiskDistribution cfg) { switch (cfg) { @@ -155,8 +145,8 @@ Distribution::configure(const vespa::config::content::StorDistributionConfig& co for (uint32_t i=0, n=config.group.size(); i<n; ++i) { const ConfigGroup& cg(config.group[i]); std::vector<uint16_t> path; - if (nodeGraph.get() != 0) { - path = getGroupPath(cg.index); + if (nodeGraph.get() != nullptr) { + path = DistributionConfigUtil::getGroupPath(cg.index); } bool isLeafGroup = (cg.nodes.size() > 0); std::unique_ptr<Group> group; @@ -179,7 +169,7 @@ Distribution::configure(const vespa::config::content::StorDistributionConfig& co if (path.empty()) { nodeGraph = std::move(group); } else { - assert(nodeGraph.get() != 0); + assert(nodeGraph.get() != nullptr); Group* parent = nodeGraph.get(); for (uint32_t j=0; j<path.size() - 1; ++j) { parent = parent->getSubGroups()[path[j]]; @@ -187,7 +177,7 @@ Distribution::configure(const vespa::config::content::StorDistributionConfig& co parent->addSubGroup(std::move(group)); } } - if (nodeGraph.get() == 0) { + if (nodeGraph.get() == nullptr) { throw vespalib::IllegalStateException( "Got config that didn't seem to specify even a root group. Must " "have a root group at minimum:\n" @@ -462,14 +452,14 @@ Distribution::getIdealGroups(const document::BucketId& bucket, tmpResults.pop_back(); } } - while (tmpResults.back()._group == 0) { + while (tmpResults.back()._group == nullptr) { tmpResults.pop_back(); } for (uint32_t i=0, n=tmpResults.size(); i<n; ++i) { ScoredGroup& group(tmpResults[i]); // This should never happen. Config should verify that each group // has enough groups beneath them. - assert(group._group != 0); + assert(group._group != nullptr); getIdealGroups(bucket, clusterState, *group._group, redundancyArray[i], results); } @@ -478,14 +468,11 @@ Distribution::getIdealGroups(const document::BucketId& bucket, const Group* Distribution::getIdealDistributorGroup(const document::BucketId& bucket, const ClusterState& clusterState, - const Group& parent, - uint16_t redundancy) const + const Group& parent) const { if (parent.isLeafGroup()) { return &parent; } - const Group::Distribution& redundancyArray( - parent.getDistribution(redundancy)); ScoredGroup result(0, 0); uint32_t seed(getGroupSeed(bucket, clusterState, parent)); RandomGen random(seed); @@ -497,8 +484,8 @@ Distribution::getIdealDistributorGroup(const document::BucketId& bucket, while (it->first < currentIndex++) random.nextDouble(); double score = random.nextDouble(); if (it->second->getCapacity() != 1) { - // Capacity shouldn't possibly be 0. - // Verified in Group::setCapacity() + // Capacity shouldn't possibly be 0. + // Verified in Group::setCapacity() score = std::pow(score, 1.0 / it->second->getCapacity().getValue()); } if (score > result._score) { @@ -509,11 +496,10 @@ Distribution::getIdealDistributorGroup(const document::BucketId& bucket, } } } - if (result._group == 0) { - return 0; + if (result._group == nullptr) { + return nullptr; } - return getIdealDistributorGroup( - bucket, clusterState, *result._group, redundancyArray[0]); + return getIdealDistributorGroup(bucket, clusterState, *result._group); } bool @@ -567,9 +553,8 @@ Distribution::getIdealNodes(const NodeType& nodeType, _groupDistribution); } else { seed = getDistributorSeed(bucket, clusterState); - const Group* group(getIdealDistributorGroup( - bucket, clusterState, *_nodeGraph, redundancy)); - if (group == 0) { + const Group* group(getIdealDistributorGroup(bucket, clusterState, *_nodeGraph)); + if (group == nullptr) { vespalib::asciistream ss; ss << "There is no legal distributor target in state with version " << clusterState.getVersion(); @@ -679,7 +664,7 @@ Distribution::getIdealDistributorNode( std::vector<uint16_t> nodes; getIdealNodes(NodeType::DISTRIBUTOR, state, bucket, nodes, upStates); assert(nodes.size() <= 1); - if (nodes.size() == 0) { + if (nodes.empty()) { vespalib::asciistream ss; ss << "There is no legal distributor target in state with version " << state.getVersion(); @@ -695,7 +680,7 @@ Distribution::splitNodesIntoLeafGroups(IndexList nodeList) const std::map<uint16_t, IndexList> nodes; for (uint32_t i=0, n=nodeList.size(); i<n; ++i) { const Group* group(_nodeGraph->getGroupForNode(nodeList[i])); - if (group == 0) { + if (group == nullptr) { LOGBP(warning, "Node %u is not assigned to a group. " "Should not happen?", nodeList[i]); } else { diff --git a/vdslib/src/vespa/vdslib/distribution/distribution.h b/vdslib/src/vespa/vdslib/distribution/distribution.h index c828dbff9b8..6da60e084bb 100644 --- a/vdslib/src/vespa/vdslib/distribution/distribution.h +++ b/vdslib/src/vespa/vdslib/distribution/distribution.h @@ -13,17 +13,10 @@ #include <vespa/vdslib/state/nodetype.h> #include <vespa/vespalib/util/exception.h> -namespace vespa { - namespace config { - namespace content { - namespace internal { - class InternalStorDistributionType; - } - } - } +namespace vespa::config::content::internal { + class InternalStorDistributionType; } -namespace storage { -namespace lib { +namespace storage::lib { VESPA_DEFINE_EXCEPTION(NoDistributorsAvailableException, vespalib::Exception); VESPA_DEFINE_EXCEPTION(TooFewBucketBitsInUseException, vespalib::Exception); @@ -101,8 +94,7 @@ private: const Group* getIdealDistributorGroup(const document::BucketId& bucket, const ClusterState& clusterState, - const Group& parent, - uint16_t redundancy) const; + const Group& parent) const; /** * Since distribution object may be used often in ideal state calculations @@ -205,6 +197,5 @@ public: static bool allDistributorsDown(const Group&, const ClusterState&); }; -} // lib -} // storage +} // storage::lib diff --git a/vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp new file mode 100644 index 00000000000..e700dd4e379 --- /dev/null +++ b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.cpp @@ -0,0 +1,18 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "distribution_config_util.h" +#include <vespa/vespalib/text/stringtokenizer.h> +#include <boost/lexical_cast.hpp> + +namespace storage::lib { + +std::vector<uint16_t> DistributionConfigUtil::getGroupPath(vespalib::stringref path) { + vespalib::StringTokenizer st(path, ".", ""); + std::vector<uint16_t> result(st.size()); + for (uint32_t i=0, n=result.size(); i<n; ++i) { + result[i] = boost::lexical_cast<uint16_t>(st[i]); + } + return result; +} + +} diff --git a/vdslib/src/vespa/vdslib/distribution/distribution_config_util.h b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.h new file mode 100644 index 00000000000..3466b16f275 --- /dev/null +++ b/vdslib/src/vespa/vdslib/distribution/distribution_config_util.h @@ -0,0 +1,15 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/stllike/string.h> +#include <vector> + +namespace storage::lib { + +struct DistributionConfigUtil { + // Converts an input string of the form "1.2.3" to a returned vector {1, 2, 3} + static std::vector<uint16_t> getGroupPath(vespalib::stringref path); +}; + +} diff --git a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp index 97bde0ca54f..f620dc15928 100644 --- a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp +++ b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.cpp @@ -14,10 +14,10 @@ namespace { void verifyLegal(vespalib::StringTokenizer& st, vespalib::stringref serialized) { - // First, verify sanity of the serialized string - uint32_t firstAsterix = st.size(); + // First, verify sanity of the serialized string + uint32_t firstAsterisk = st.size(); for (uint32_t i=0; i<st.size(); ++i) { - if (i > firstAsterix) { + if (i > firstAsterisk) { if (st[i] != "*") { throw vespalib::IllegalArgumentException( "Illegal distribution spec \"" + serialized + "\". " @@ -26,8 +26,8 @@ namespace { } continue; } - if (i < firstAsterix && st[i] == "*") { - firstAsterix = i; + if (i < firstAsterisk && st[i] == "*") { + firstAsterisk = i; continue; } uint32_t number = atoi(st[i].c_str()); @@ -76,21 +76,21 @@ RedundancyGroupDistribution::RedundancyGroupDistribution( const RedundancyGroupDistribution& spec, uint16_t redundancy) { - uint16_t firstAsterix = spec.getFirstAsterixIndex(); - // If redundancy is less than the group size, we only get one copy - // in redundancy groups. + uint16_t firstAsterisk = spec.getFirstAsteriskIndex(); + // If redundancy is less than the group size, we only get one copy + // in redundancy groups. if (redundancy <= spec.size()) { _values = std::vector<uint16_t>(redundancy, 1); return; } - // If not we will have one copy at least for every wanted group. + // If not we will have one copy at least for every wanted group. _values = std::vector<uint16_t>(spec.size(), 1); redundancy -= spec.size(); - // Distribute extra copies to non-asterix entries first - redundancy = divideSpecifiedCopies(0, firstAsterix, redundancy, spec._values); - // Distribute remaining copies to asterix entries - divideSpecifiedCopies(firstAsterix, spec.size(), redundancy, spec._values); - // Lastly sort, so the most copies will end up first in ideal state + // Distribute extra copies to non-asterisk entries first + redundancy = divideSpecifiedCopies(0, firstAsterisk, redundancy, spec._values); + // Distribute remaining copies to asterisk entries + divideSpecifiedCopies(firstAsterisk, spec.size(), redundancy, spec._values); + // Lastly sort, so the most copies will end up first in ideal state std::sort(_values.begin(), _values.end()); std::reverse(_values.begin(), _values.end()); assert(_values.front() >= _values.back()); @@ -111,18 +111,18 @@ RedundancyGroupDistribution::print(std::ostream& out, } uint16_t -RedundancyGroupDistribution::getFirstAsterixIndex() const +RedundancyGroupDistribution::getFirstAsteriskIndex() const { if (_values.empty() || _values.back() != 0) { throw vespalib::IllegalArgumentException( "Invalid spec given. No asterisk entries found.", VESPA_STRLOC); } - uint16_t firstAsterix = _values.size() - 1; - while (firstAsterix > 0 && _values[firstAsterix - 1] == 0) { - --firstAsterix; + uint16_t firstAsterisk = _values.size() - 1; + while (firstAsterisk > 0 && _values[firstAsterisk - 1] == 0) { + --firstAsterisk; } - return firstAsterix; + return firstAsterisk; } uint16_t diff --git a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h index 5dde7cf378d..33f895cadf0 100644 --- a/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h +++ b/vdslib/src/vespa/vdslib/distribution/redundancygroupdistribution.h @@ -41,7 +41,7 @@ public: void print(std::ostream&, bool verbose, const std::string& indent) const override; private: - uint16_t getFirstAsterixIndex() const; + uint16_t getFirstAsteriskIndex() const; uint16_t divideSpecifiedCopies( uint16_t start, uint16_t end, uint16_t redundancy, const std::vector<uint16_t>& maxValues); diff --git a/vespa-application-maven-plugin/pom.xml b/vespa-application-maven-plugin/pom.xml index 2464c8208ad..980c8eee60f 100644 --- a/vespa-application-maven-plugin/pom.xml +++ b/vespa-application-maven-plugin/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespa-application-maven-plugin</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespa-documentgen-plugin/pom.xml b/vespa-documentgen-plugin/pom.xml index 4e96a0475f7..718b19ae994 100644 --- a/vespa-documentgen-plugin/pom.xml +++ b/vespa-documentgen-plugin/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespa-documentgen-plugin</artifactId> <packaging>maven-plugin</packaging> diff --git a/vespa-hadoop/pom.xml b/vespa-hadoop/pom.xml index b9fbd589525..62d9010b9fd 100644 --- a/vespa-hadoop/pom.xml +++ b/vespa-hadoop/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespa-hadoop</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespa-http-client/pom.xml b/vespa-http-client/pom.xml index 15d72b7c1c2..c336c641352 100644 --- a/vespa-http-client/pom.xml +++ b/vespa-http-client/pom.xml @@ -7,6 +7,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespa-http-client</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespa_feed_perf/pom.xml b/vespa_feed_perf/pom.xml index f54b2df9b8a..436dcc06f3d 100644 --- a/vespa_feed_perf/pom.xml +++ b/vespa_feed_perf/pom.xml @@ -13,6 +13,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespa_feed_perf</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespa_jersey2/pom.xml b/vespa_jersey2/pom.xml index 3250cd8a41f..781075fd041 100644 --- a/vespa_jersey2/pom.xml +++ b/vespa_jersey2/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespa_jersey2</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespabase/conf/default-env.txt.in b/vespabase/conf/default-env.txt.in index 5b144e4c301..38a4d0cded5 100644 --- a/vespabase/conf/default-env.txt.in +++ b/vespabase/conf/default-env.txt.in @@ -1,3 +1,4 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. fallback VESPA_HOME @CMAKE_INSTALL_PREFIX@ override VESPA_USER vespa +override cloudconfig_server__disable_filedistributor true diff --git a/vespaclient-container-plugin/pom.xml b/vespaclient-container-plugin/pom.xml index 59895438038..3472f3fcdc5 100644 --- a/vespaclient-container-plugin/pom.xml +++ b/vespaclient-container-plugin/pom.xml @@ -9,6 +9,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespaclient-container-plugin</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespaclient-core/pom.xml b/vespaclient-core/pom.xml index bdb15825fbb..2e427364bd6 100644 --- a/vespaclient-core/pom.xml +++ b/vespaclient-core/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespaclient-core</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespaclient-java/pom.xml b/vespaclient-java/pom.xml index 8cde41d53d7..d692a6ed57f 100644 --- a/vespaclient-java/pom.xml +++ b/vespaclient-java/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespaclient-java</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespajlib/pom.xml b/vespajlib/pom.xml index 1f98a5e4c02..81c385c96ab 100644 --- a/vespajlib/pom.xml +++ b/vespajlib/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespajlib</artifactId> <packaging>container-plugin</packaging> diff --git a/vespajlib/src/main/java/com/yahoo/io/IOUtils.java b/vespajlib/src/main/java/com/yahoo/io/IOUtils.java index 2572842b213..febe02cb33e 100644 --- a/vespajlib/src/main/java/com/yahoo/io/IOUtils.java +++ b/vespajlib/src/main/java/com/yahoo/io/IOUtils.java @@ -3,6 +3,7 @@ package com.yahoo.io; import java.io.*; +import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.util.List; @@ -155,33 +156,22 @@ public abstract class IOUtils { /** * Copies a file to another file. * If the out file exists it will be overwritten. - * NOTE: Not an optimal implementation currently. * * @throws IOException if copying fails */ public static void copy(String inFile, String outFile) throws IOException { - BufferedReader reader=null; - BufferedWriter writer=null; - - try { - reader = createReader(inFile); - writer = createWriter(outFile, false); - int c; - while (-1 != (c = reader.read()) ) - writer.write(c); - } finally { - closeReader(reader); - closeWriter(writer); - } + copy(new File(inFile), new File(outFile)); } /** * Copies a file to another file. * If the out file exists it will be overwritten. - * NOTE: Not an optimal implementation currently. */ public static void copy(File inFile, File outFile) throws IOException { - copy(inFile.toString(),outFile.toString()); + try (FileChannel sourceChannel = new FileInputStream(inFile).getChannel(); + FileChannel destChannel = new FileOutputStream(outFile).getChannel()) { + destChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); + } } /** @@ -275,8 +265,8 @@ public abstract class IOUtils { } /** - * Returns the number of line in a file. - * If the files does not exists, 0 is returned + * Returns the number of lines in a file. + * If the file does not exists, 0 is returned */ public static int countLines(String file) { BufferedReader reader = null; @@ -292,7 +282,6 @@ public abstract class IOUtils { } finally { closeReader(reader); } - } /** diff --git a/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java b/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java index 3a8b0dde1c1..8955bd9ea05 100644 --- a/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java +++ b/vespajlib/src/test/java/com/yahoo/io/IOUtilsTestCase.java @@ -1,15 +1,23 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.io; +import org.junit.Test; + import java.io.*; import java.util.Arrays; import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * @author bratseth */ -public class IOUtilsTestCase extends junit.framework.TestCase { +public class IOUtilsTestCase { + @Test public void testCloseNUllDoesNotFail() { IOUtils.closeWriter(null); IOUtils.closeReader(null); @@ -17,12 +25,14 @@ public class IOUtilsTestCase extends junit.framework.TestCase { IOUtils.closeOutputStream(null); } + @Test public void testFileWriter() throws IOException { IOUtils.writeFile("temp1.txt", "hello",false); assertEquals("hello", IOUtils.readFile(new File("temp1.txt"))); new File("temp1.txt").delete(); } + @Test public void testFileWriterWithoutEncoding() throws IOException { BufferedWriter writer=null; try { @@ -36,6 +46,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { new File("temp2.txt").delete(); } + @Test public void testFileWriterWithoutEncodingFromFileName() throws IOException { BufferedWriter writer=null; try { @@ -49,12 +60,14 @@ public class IOUtilsTestCase extends junit.framework.TestCase { new File("temp3.txt").delete(); } + @Test public void testFileCounting() throws IOException { IOUtils.writeFile("temp4.txt","hello\nworld",false); assertEquals(2,IOUtils.countLines("temp4.txt")); new File("temp4.txt").delete(); } + @Test public void testFileCopy() throws IOException { IOUtils.writeFile("temp5.txt","hello",false); IOUtils.copy(new File("temp5.txt"), new File("temp5copy.txt")); @@ -63,6 +76,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { new File("temp5copy.txt").delete(); } + @Test public void testFileCopyWithLineCap() throws IOException { IOUtils.writeFile("temp6.txt","hello\nyou\nworld",false); IOUtils.copy("temp6.txt","temp6copy.txt",2); @@ -71,6 +85,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { new File("temp6copy.txt").delete(); } + @Test public void testGetLines() throws IOException { IOUtils.writeFile("temp7.txt","hello\nworld",false); List<String> lines=IOUtils.getLines("temp7.txt"); @@ -80,6 +95,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { new File("temp7.txt").delete(); } + @Test public void testFileWriterAppend() throws IOException { boolean append=true; IOUtils.writeFile("temp8.txt", "hello",!append); @@ -95,6 +111,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { new File("temp8.txt").delete(); } + @Test public void testCloseAllReaders() throws IOException { StringReader reader1=new StringReader("hello"); StringReader reader2=new StringReader("world"); @@ -115,6 +132,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { } } + @Test public void testDirCopying() throws IOException { IOUtils.writeFile("temp1/temp1.txt","hello",false); IOUtils.writeFile("temp1/temp2.txt","world",false); @@ -127,6 +145,7 @@ public class IOUtilsTestCase extends junit.framework.TestCase { assertTrue(!new File("temp2").exists()); } + @Test public void testDirCopyingWithFilter() throws IOException { IOUtils.writeFile("temp1/temp1.txt","hello",false); IOUtils.writeFile("temp1/temp2.txt","world",false); diff --git a/vespalib/src/vespa/vespalib/testkit/test_hook.h b/vespalib/src/vespa/vespalib/testkit/test_hook.h index 336e965b0b1..28419c0b31b 100644 --- a/vespalib/src/vespa/vespalib/testkit/test_hook.h +++ b/vespalib/src/vespa/vespalib/testkit/test_hook.h @@ -2,7 +2,7 @@ #pragma once -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/count_down_latch.h> #include <vespa/vespalib/util/barrier.h> #include <string> #include <vector> diff --git a/vespalib/src/vespa/vespalib/testkit/time_bomb.h b/vespalib/src/vespa/vespalib/testkit/time_bomb.h index 5b39e27db79..8412e4b8661 100644 --- a/vespalib/src/vespa/vespalib/testkit/time_bomb.h +++ b/vespalib/src/vespa/vespalib/testkit/time_bomb.h @@ -2,7 +2,7 @@ #pragma once -#include <vespa/vespalib/util/sync.h> +#include <vespa/vespalib/util/gate.h> #include <thread> namespace vespalib { diff --git a/vespalib/src/vespa/vespalib/util/count_down_latch.h b/vespalib/src/vespa/vespalib/util/count_down_latch.h new file mode 100644 index 00000000000..66ef1e44cee --- /dev/null +++ b/vespalib/src/vespa/vespalib/util/count_down_latch.h @@ -0,0 +1,95 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <mutex> +#include <condition_variable> +#include <chrono> + +namespace vespalib { + +/** + * A countdown latch helps one or more threads wait for the completion + * of a number of operations performed by other threads. Specifically, + * any thread invoking the await method will block until the countDown + * method has been invoked an appropriate number of times. The + * countdown latch is created with a count. Each invocation of + * countDown will reduce the current count. When the count reaches 0, + * the threads blocked in await will be unblocked. When the count is + * 0, additional invocations of await will not block and additional + * invocations of countDown will have no effect. + **/ +class CountDownLatch +{ +private: + std::mutex _lock; + std::condition_variable _cond; + uint32_t _count; + + CountDownLatch(const CountDownLatch &rhs) = delete; + CountDownLatch(CountDownLatch &&rhs) = delete; + CountDownLatch &operator=(const CountDownLatch &rhs) = delete; + CountDownLatch &operator=(CountDownLatch &&rhs) = delete; + +public: + /** + * Create a countdown latch with the given initial count. + * + * @param cnt initial count + **/ + CountDownLatch(uint32_t cnt) : _lock(), _cond(), _count(cnt) {} + + /** + * Count down this latch. When the count reaches 0, all threads + * blocked in the await method will be unblocked. + **/ + void countDown() { + std::lock_guard<std::mutex> guard(_lock); + if (_count != 0) { + --_count; + if (_count == 0) { + _cond.notify_all(); + } + } + } + + /** + * Wait for this latch to count down to 0. This method will block + * until the countDown method has been invoked enough times to + * reduce the count to 0. + **/ + void await() { + std::unique_lock<std::mutex> guard(_lock); + _cond.wait(guard, [this]() { return (_count == 0); }); + } + + /** + * Wait for this latch to count down to 0. This method will block + * until the countDown method has been invoked enough times to + * reduce the count to 0 or the given amount of time has elapsed. + * + * @param maxwait the maximum number of milliseconds to wait + * @return true if the counter reached 0, false if we timed out + **/ + bool await(int maxwait) { + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(maxwait); + std::unique_lock<std::mutex> guard(_lock); + return _cond.wait_until(guard, deadline, [this]() { return (_count == 0); }); + } + + /** + * Obtain the current count for this latch. This method is mostly + * useful for debugging and testing. + * + * @return current count + **/ + uint32_t getCount() const { return _count; } + + /** + * Empty. Needs to be virtual to reduce compiler warnings. + **/ + virtual ~CountDownLatch() = default; +}; + +} // namespace vespalib + diff --git a/vespalib/src/vespa/vespalib/util/gate.h b/vespalib/src/vespa/vespalib/util/gate.h new file mode 100644 index 00000000000..7d913a7a039 --- /dev/null +++ b/vespalib/src/vespa/vespalib/util/gate.h @@ -0,0 +1,22 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "count_down_latch.h" + +namespace vespalib { + +/** + * A gate is a countdown latch with an initial count of 1, indicating + * that we are only waiting for a single operation to complete. + **/ +class Gate : public CountDownLatch +{ +public: + /** + * Sets the initial count to 1. + **/ + Gate() : CountDownLatch(1) {} +}; + +} // namespace vespalib diff --git a/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h b/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h index 8fb7e1d04fc..4fa73c1112f 100644 --- a/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h +++ b/vespalib/src/vespa/vespalib/util/simple_thread_bundle.h @@ -3,6 +3,7 @@ #pragma once #include "sync.h" +#include "count_down_latch.h" #include "thread.h" #include "runnable.h" #include "thread_bundle.h" diff --git a/vespalib/src/vespa/vespalib/util/sync.h b/vespalib/src/vespa/vespalib/util/sync.h index c3d8ea86aec..e3f3d44122c 100644 --- a/vespalib/src/vespa/vespalib/util/sync.h +++ b/vespalib/src/vespa/vespalib/util/sync.h @@ -527,108 +527,5 @@ public: } }; - -/** - * A countdown latch helps one or more threads wait for the completion - * of a number of operations performed by other threads. Specifically, - * any thread invoking the await method will block until the countDown - * method has been invoked an appropriate number of times. The - * countdown latch is created with a count. Each invocation of - * countDown will reduce the current count. When the count reaches 0, - * the threads blocked in await will be unblocked. When the count is - * 0, additional invocations of await will not block and additional - * invocations of countDown will have no effect. - **/ -class CountDownLatch -{ -private: - Monitor _monitor; - uint32_t _count; - - CountDownLatch(const CountDownLatch &rhs) = delete; - CountDownLatch &operator=(const CountDownLatch &rhs) = delete; - -public: - /** - * Create a countdown latch with the given initial count. - * - * @param cnt initial count - **/ - CountDownLatch(uint32_t cnt) : _monitor(), _count(cnt) {} - - /** - * Count down this latch. When the count reaches 0, all threads - * blocked in the await method will be unblocked. - **/ - void countDown() { - MonitorGuard guard(_monitor); - if (_count == 0) { - return; - } - --_count; - if (_count == 0) { - guard.broadcast(); - } - } - - /** - * Wait for this latch to count down to 0. This method will block - * until the countDown method has been invoked enough times to - * reduce the count to 0. - **/ - void await() { - MonitorGuard guard(_monitor); - while (_count != 0) { - guard.wait(); - } - } - - /** - * Wait for this latch to count down to 0. This method will block - * until the countDown method has been invoked enough times to - * reduce the count to 0 or the given amount of time has elapsed. - * - * @param maxwait the maximum number of milliseconds to wait - * @return true if the counter reached 0, false if we timed out - **/ - bool await(int maxwait) { - MonitorGuard guard(_monitor); - TimedWaiter waiter(guard, maxwait); - while (_count != 0 && waiter.hasTime()) { - waiter.wait(); - } - return (_count == 0); - } - - /** - * Obtain the current count for this latch. This method is mostly - * useful for debugging and testing. - * - * @return current count - **/ - uint32_t getCount() const { - return _count; - } - - /** - * Empty. Needs to be virtual to reduce compiler warnings. - **/ - virtual ~CountDownLatch() = default; -}; - - -/** - * A gate is a countdown latch with an initial count of 1, indicating - * that we are only waiting for a single operation to complete. - **/ -class Gate : public CountDownLatch -{ -public: - /** - * Sets the initial count to 1. - **/ - Gate() : CountDownLatch(1) {} -}; - } // namespace vespalib diff --git a/vespalib/src/vespa/vespalib/util/thread.h b/vespalib/src/vespa/vespalib/util/thread.h index 426057be85a..d24a7e6f174 100644 --- a/vespalib/src/vespa/vespalib/util/thread.h +++ b/vespalib/src/vespa/vespalib/util/thread.h @@ -3,6 +3,7 @@ #pragma once #include "sync.h" +#include "gate.h" #include "runnable.h" #include "active.h" #include <vespa/fastos/thread.h> diff --git a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h index 5048a9c0436..1d8545781f5 100644 --- a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h +++ b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h @@ -6,6 +6,7 @@ #include "eventbarrier.hpp" #include "arrayqueue.hpp" #include "sync.h" +#include "gate.h" #include "runnable.h" #include <memory> #include <vector> diff --git a/vespalog/pom.xml b/vespalog/pom.xml index 6cea5ab83d9..6443769afbe 100644 --- a/vespalog/pom.xml +++ b/vespalog/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vespalog</artifactId> <version>6-SNAPSHOT</version> diff --git a/vespamalloc/src/tests/test1/testatomic.cpp b/vespamalloc/src/tests/test1/testatomic.cpp index 818af5fe673..54a0b406116 100644 --- a/vespamalloc/src/tests/test1/testatomic.cpp +++ b/vespamalloc/src/tests/test1/testatomic.cpp @@ -18,7 +18,7 @@ TEST("verify lock freeness of atomics"){ // See https://gcc.gnu.org/ml/gcc-patches/2017-01/msg02344.html for background ASSERT_TRUE(taggedPtr.is_lock_free()); #else - ASSERT_FALSE(taggedPtr.is_lock_free()); + ASSERT_TRUE(taggedPtr.is_lock_free() || !taggedPtr.is_lock_free()); #endif } diff --git a/vsm/pom.xml b/vsm/pom.xml index 8c5b006aeec..3c491906060 100644 --- a/vsm/pom.xml +++ b/vsm/pom.xml @@ -8,6 +8,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>vsm</artifactId> <version>6-SNAPSHOT</version> diff --git a/yolean/pom.xml b/yolean/pom.xml index d64ea29a95c..850daae6fea 100644 --- a/yolean/pom.xml +++ b/yolean/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>yolean</artifactId> <version>6-SNAPSHOT</version> diff --git a/zkfacade/pom.xml b/zkfacade/pom.xml index 45ba8c32372..f9cb8c42688 100644 --- a/zkfacade/pom.xml +++ b/zkfacade/pom.xml @@ -6,6 +6,7 @@ <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>zkfacade</artifactId> <packaging>container-plugin</packaging> |