diff options
104 files changed, 1156 insertions, 656 deletions
diff --git a/application/pom.xml b/application/pom.xml index d1aea1d6a78..d404cb9bbf5 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -133,8 +133,8 @@ <!-- START JETTY embedded jars --> <dependency> - <groupId>org.eclipse.jetty.alpn</groupId> - <artifactId>alpn-api</artifactId> + <groupId>org.eclipse.jetty.http2</groupId> + <artifactId>http2-common</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty.http2</groupId> @@ -146,35 +146,43 @@ </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-alpn-server</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-client</artifactId> - <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-continuation</artifactId> - <scope>test</scope> + <artifactId>jetty-http</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-io</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-jmx</artifactId> - <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> - <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> - <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlets</artifactId> - <scope>test</scope> + <artifactId>jetty-util</artifactId> </dependency> <dependency> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>jetty-jakarta-servlet-api</artifactId> + </dependency> + <!-- END JETTY embedded jars --> + + <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> @@ -184,8 +192,6 @@ <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> - <!-- END JETTY embedded jars --> - </dependencies> <build> diff --git a/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml index 78178c9f5dc..3dc0f370e53 100644 --- a/cloud-tenant-base-dependencies-enforcer/pom.xml +++ b/cloud-tenant-base-dependencies-enforcer/pom.xml @@ -44,8 +44,7 @@ <javax.servlet-api.version>3.1.0</javax.servlet-api.version> <javax.ws.rs-api.version>2.0.1</javax.ws.rs-api.version> <jaxb.version>2.3.0</jaxb.version> - <jetty.version>9.4.49.v20220914</jetty.version> - <jetty-alpn.version>1.1.3.v20160715</jetty-alpn.version> + <jetty.version>11.0.13</jetty.version> <org.lz4.version>1.8.0</org.lz4.version> <org.json.version>20220320</org.json.version> <!-- TODO: Remove on Vespa 9 --> <slf4j.version>1.7.32</slf4j.version> <!-- WARNING: when updated, also update c.y.v.tenant:base pom --> @@ -206,22 +205,20 @@ <include>org.bouncycastle:bcpkix-jdk18on:${bouncycastle.version}:test</include> <include>org.bouncycastle:bcprov-jdk18on:${bouncycastle.version}:test</include> <include>org.bouncycastle:bcutil-jdk18on:${bouncycastle.version}:test</include> - <include>org.eclipse.jetty.alpn:alpn-api:jar:${jetty-alpn.version}:test</include> <include>org.eclipse.jetty.http2:http2-common:${jetty.version}:test</include> <include>org.eclipse.jetty.http2:http2-hpack:${jetty.version}:test</include> <include>org.eclipse.jetty.http2:http2-server:${jetty.version}:test</include> + <include>org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2:test</include> + <include>org.eclipse.jetty:jetty-alpn-client:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-alpn-java-server:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-alpn-server:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-client:${jetty.version}:test</include> - <include>org.eclipse.jetty:jetty-continuation:jar:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-http:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-io:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-jmx:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-security:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-server:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-servlet:${jetty.version}:test</include> - <include>org.eclipse.jetty:jetty-servlets:jar:${jetty.version}:test</include> - <include>org.eclipse.jetty:jetty-util-ajax:jar:${jetty.version}:test</include> <include>org.eclipse.jetty:jetty-util:${jetty.version}:test</include> <include>org.hamcrest:hamcrest-core:1.3:test</include> diff --git a/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java b/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java index e0ce9917179..37da07f8227 100644 --- a/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java +++ b/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java @@ -44,17 +44,26 @@ public class TensorFieldProcessor extends Processor { private void validateIndexingScripsForTensorField(SDField field) { if (field.doesIndexing() && !isTensorTypeThatSupportsHnswIndex(field)) { fail(schema, field, "A tensor of type '" + tensorTypeToString(field) + "' does not support having an 'index'. " + - "Currently, only tensors with 1 indexed dimension supports that."); + "Currently, only tensors with 1 indexed dimension or 1 mapped + 1 indexed dimension support that."); } } private boolean isTensorTypeThatSupportsHnswIndex(ImmutableSDField field) { var type = ((TensorDataType)field.getDataType()).getTensorType(); - // Tensors with 1 indexed dimension supports a hnsw index (used for approximate nearest neighbor search). + // Tensors with 1 indexed dimension support hnsw index (used for approximate nearest neighbor search). if ((type.dimensions().size() == 1) && type.dimensions().get(0).isIndexed()) { return true; } + // Tensors with 1 mapped + 1 indexed dimension support hnsw index (aka multiple vectors per document). + if (type.dimensions().size() == 2) { + var a = type.dimensions().get(0); + var b = type.dimensions().get(1); + if ((a.isMapped() && b.isIndexed()) || + (a.isIndexed() && b.isMapped())) { + return true; + } + } return false; } diff --git a/config-model/src/test/java/com/yahoo/schema/processing/TensorFieldTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/TensorFieldTestCase.java index 60e1e35fb2e..b7817900dac 100644 --- a/config-model/src/test/java/com/yahoo/schema/processing/TensorFieldTestCase.java +++ b/config-model/src/test/java/com/yahoo/schema/processing/TensorFieldTestCase.java @@ -35,7 +35,7 @@ public class TensorFieldTestCase { } catch (IllegalArgumentException e) { assertEquals("For schema 'test', field 'f1': A tensor of type 'tensor(x{})' does not support having an 'index'. " + - "Currently, only tensors with 1 indexed dimension supports that.", + "Currently, only tensors with 1 indexed dimension or 1 mapped + 1 indexed dimension support that.", e.getMessage()); } } @@ -86,6 +86,12 @@ public class TensorFieldTestCase { } @Test + void tensor_with_one_mapped_and_one_indexed_dimension_can_have_hnsw_index() throws ParseException { + assertHnswIndexParams("tensor(x{},y[64])", "", 16, 200); + assertHnswIndexParams("tensor(x[64],y{})", "", 16, 200); + } + + @Test void hnsw_index_parameters_can_be_specified() throws ParseException { assertHnswIndexParams("index { hnsw { max-links-per-node: 32 } }", 32, 200); assertHnswIndexParams("index { hnsw { neighbors-to-explore-at-insert: 300 } }", 16, 300); @@ -157,7 +163,11 @@ public class TensorFieldTestCase { } private void assertHnswIndexParams(String indexSpec, int maxLinksPerNode, int neighborsToExploreAtInsert) throws ParseException { - var sd = getSdWithIndexSpec(indexSpec); + assertHnswIndexParams("tensor(x[64])", indexSpec, maxLinksPerNode, neighborsToExploreAtInsert); + } + + private void assertHnswIndexParams(String tensorType, String indexSpec, int maxLinksPerNode, int neighborsToExploreAtInsert) throws ParseException { + var sd = getSdWithIndexSpec(tensorType, indexSpec); var search = createFromString(sd).getSchema(); var attr = search.getAttribute("t1"); var params = attr.hnswIndexParams(); @@ -166,8 +176,8 @@ public class TensorFieldTestCase { assertEquals(neighborsToExploreAtInsert, params.get().neighborsToExploreAtInsert()); } - private String getSdWithIndexSpec(String indexSpec) { - return getSd(joinLines("field t1 type tensor(x[64]) {", + private String getSdWithIndexSpec(String tensorType, String indexSpec) { + return getSd(joinLines("field t1 type " + tensorType + " {", " indexing: attribute | index", " " + indexSpec, "}")); diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/CloudAccount.java b/config-provisioning/src/main/java/com/yahoo/config/provision/CloudAccount.java index 974e5203e76..215afbca255 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/CloudAccount.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/CloudAccount.java @@ -2,6 +2,7 @@ package com.yahoo.config.provision; import ai.vespa.validation.PatternedStringWrapper; +import ai.vespa.validation.Validation; import java.util.regex.Pattern; @@ -14,11 +15,23 @@ public class CloudAccount extends PatternedStringWrapper<CloudAccount> { private static final String EMPTY = ""; private static final String AWS_ACCOUNT_ID = "[0-9]{12}"; + private static final Pattern AWS_ACCOUNT_ID_PATTERN = Pattern.compile(AWS_ACCOUNT_ID); private static final String GCP_PROJECT_ID = "[a-z][a-z0-9-]{4,28}[a-z0-9]"; + private static final Pattern GCP_PROJECT_ID_PATTERN = Pattern.compile(GCP_PROJECT_ID); /** Empty value. When this is used, either implicitly or explicitly, the zone will use its default account */ public static final CloudAccount empty = new CloudAccount("", EMPTY, "cloud account"); + /** Verifies accountId is a valid AWS account ID, or throw an IllegalArgumentException. */ + public static void requireAwsAccountId(String accountId) { + Validation.requireMatch(accountId, "AWS account ID", AWS_ACCOUNT_ID_PATTERN); + } + + /** Verifies accountId is a valid GCP project ID, or throw an IllegalArgumentException. */ + public static void requireGcpProjectId(String projectId) { + Validation.requireMatch(projectId, "GCP project ID", GCP_PROJECT_ID_PATTERN); + } + private CloudAccount(String value, String regex, String description) { super(value, Pattern.compile("^(" + regex + ")$"), description); } @@ -34,6 +47,16 @@ public class CloudAccount extends PatternedStringWrapper<CloudAccount> { !equals(zone.cloud().account()); } + /** Verifies this account is a valid AWS account ID, or throw an IllegalArgumentException. */ + public void requireAwsAccountId() { + requireAwsAccountId(value()); + } + + /** Verifies this account is a valid GCP project ID, or throw an IllegalArgumentException. */ + public void requireGcpProjectId() { + requireGcpProjectId(value()); + } + public static CloudAccount from(String cloudAccount) { return switch (cloudAccount) { // Tenants are allowed to specify "default" in services.xml. diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java index 1e90f3974a5..3705c167960 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java @@ -10,7 +10,6 @@ import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Inspector; import com.yahoo.slime.ObjectTraverser; import com.yahoo.slime.Type; - import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -21,7 +20,6 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.Stack; import java.util.logging.Logger; @@ -60,6 +58,8 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> { stack.push(new NamedBuilder(rootBuilder)); try { handleValue(payload.getSlime().get()); + } catch (FileReferenceDoesNotExistException e) { + throw e; } catch (Exception e) { throw new RuntimeException("Not able to create config builder for payload '" + payload.toString() + "'", e); } diff --git a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileReferenceDoesNotExistException.java b/config/src/main/java/com/yahoo/vespa/config/FileReferenceDoesNotExistException.java index 95aa07d14a7..b8767e6deb1 100644 --- a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileReferenceDoesNotExistException.java +++ b/config/src/main/java/com/yahoo/vespa/config/FileReferenceDoesNotExistException.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.filedistribution.fileacquirer; +package com.yahoo.vespa.config; /** * @author Tony Vaagenes @@ -8,7 +8,7 @@ public class FileReferenceDoesNotExistException extends RuntimeException { public final String fileReference; - FileReferenceDoesNotExistException(String fileReference) { + public FileReferenceDoesNotExistException(String fileReference) { super("Could not retrieve file with file reference '" + fileReference + "'"); this.fileReference = fileReference; } diff --git a/container-core/pom.xml b/container-core/pom.xml index bdcaab3900b..2b1d2253534 100644 --- a/container-core/pom.xml +++ b/container-core/pom.xml @@ -14,6 +14,7 @@ <artifactId>container-core</artifactId> <version>8-SNAPSHOT</version> <packaging>container-plugin</packaging> + <dependencies> <!-- COMPILE scope --> @@ -139,40 +140,119 @@ <!-- START JETTY embedded jars --> <dependency> - <groupId>org.eclipse.jetty.alpn</groupId> - <artifactId>alpn-api</artifactId> + <groupId>org.eclipse.jetty.http2</groupId> + <artifactId>http2-common</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty.http2</groupId> <artifactId>http2-server</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> + <!-- Required for JDK9ServerALPNProcessor through ServiceLoader API --> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-alpn-java-server</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-alpn-server</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-client</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-continuation</artifactId> + <artifactId>jetty-http</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-io</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-jmx</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlets</artifactId> + <artifactId>jetty-util</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>jetty-jakarta-servlet-api</artifactId> </dependency> <!-- END JETTY embedded jars --> @@ -281,11 +361,6 @@ <scope>provided</scope> </dependency> <dependency> - <groupId>javax.servlet</groupId> - <artifactId>javax.servlet-api</artifactId> - <scope>provided</scope> - </dependency> - <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <scope>provided</scope> diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/utils/MultiPartFormParser.java b/container-core/src/main/java/com/yahoo/container/jdisc/utils/MultiPartFormParser.java index 47ff229cc7b..57fbd5eb96c 100644 --- a/container-core/src/main/java/com/yahoo/container/jdisc/utils/MultiPartFormParser.java +++ b/container-core/src/main/java/com/yahoo/container/jdisc/utils/MultiPartFormParser.java @@ -3,9 +3,9 @@ package com.yahoo.container.jdisc.utils; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.yolean.Exceptions; -import org.eclipse.jetty.http.MultiPartFormInputStream; +import jakarta.servlet.http.Part; +import org.eclipse.jetty.server.MultiPartFormInputStream; -import javax.servlet.http.Part; import java.io.IOException; import java.io.InputStream; import java.util.Map; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/Cookie.java b/container-core/src/main/java/com/yahoo/jdisc/http/Cookie.java index b194124294c..c2faa1cd10a 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/Cookie.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/Cookie.java @@ -2,7 +2,7 @@ package com.yahoo.jdisc.http; import org.eclipse.jetty.http.HttpCookie; -import org.eclipse.jetty.server.CookieCutter; +import org.eclipse.jetty.server.Cookies; import java.util.Arrays; import java.util.HashSet; @@ -180,7 +180,7 @@ public class Cookie { } public static List<Cookie> fromCookieHeader(String headerVal) { - CookieCutter cookieCutter = new CookieCutter(); + Cookies cookieCutter = new Cookies(); cookieCutter.addCookieField(headerVal); return Arrays.stream(cookieCutter.getCookies()) .map(servletCookie -> { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/HttpRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/HttpRequest.java index 598a924b327..4ad38a9f965 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/HttpRequest.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/HttpRequest.java @@ -8,15 +8,14 @@ import com.yahoo.jdisc.handler.ContentChannel; import com.yahoo.jdisc.handler.RequestHandler; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.service.CurrentContainer; -import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; import java.security.Principal; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -116,15 +115,10 @@ public class HttpRequest extends Request { } private static Map<String, List<String>> getUriQueryParameters(URI uri) { - MultiMap<String> queryParameters = new MultiMap<>(); - new HttpURI(uri).decodeQueryTo(queryParameters); - - // Do a deep copy so we do not leak Jetty classes outside - Map<String, List<String>> deepCopiedQueryParameters = new HashMap<>(); - for (Map.Entry<String, List<String>> entry : queryParameters.entrySet()) { - deepCopiedQueryParameters.put(entry.getKey(), new ArrayList<>(entry.getValue())); - } - return deepCopiedQueryParameters; + if (uri.getRawQuery() == null) return Map.of(); + MultiMap<String> params = new MultiMap<>(); + UrlEncoded.decodeUtf8To(uri.getRawQuery(), params); + return Map.copyOf(params); } public Method getMethod() { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java index 13a63efeaa9..5b51eeee7d6 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java @@ -8,6 +8,7 @@ import com.yahoo.container.logging.RequestLog; import com.yahoo.container.logging.RequestLogEntry; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.jdisc.http.ServerConfig; +import jakarta.servlet.http.HttpServletRequest; import org.eclipse.jetty.http2.HTTP2Stream; import org.eclipse.jetty.http2.server.HttpTransportOverHTTP2; import org.eclipse.jetty.server.HttpChannel; @@ -16,7 +17,6 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.component.AbstractLifeCycle; -import javax.servlet.http.HttpServletRequest; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java index 6282e334409..f2118008af3 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java @@ -9,6 +9,8 @@ import com.yahoo.jdisc.http.ssl.impl.DefaultConnectorSsl; import com.yahoo.security.tls.MixedMode; import com.yahoo.security.tls.TransportSecurityUtils; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; @@ -137,8 +139,17 @@ public class ConnectorFactory { httpConfig.setOutputBufferSize(connectorConfig.outputBufferSize()); httpConfig.setRequestHeaderSize(connectorConfig.requestHeaderSize()); httpConfig.setResponseHeaderSize(connectorConfig.responseHeaderSize()); + + // Disable use of ByteBuffer.allocateDirect() + httpConfig.setUseInputDirectByteBuffers(false); + httpConfig.setUseOutputDirectByteBuffers(false); + + httpConfig.setHttpCompliance(HttpCompliance.RFC7230); + // TODO Vespa 9 Use default URI compliance (LEGACY == old Jetty 9.4 compliance) + httpConfig.setUriCompliance(UriCompliance.LEGACY); if (isSslEffectivelyEnabled(connectorConfig)) { - httpConfig.addCustomizer(new SecureRequestCustomizer()); + // Explicitly disable SNI checking as Jetty's SNI checking trust manager is not part of our SSLContext trust manager chain + httpConfig.addCustomizer(new SecureRequestCustomizer(false, false, -1, false)); } String serverNameFallback = connectorConfig.serverName().fallback(); if (!serverNameFallback.isBlank()) httpConfig.setServerAuthority(new HostPort(serverNameFallback)); @@ -169,12 +180,14 @@ public class ConnectorFactory { } private SslConnectionFactory newSslConnectionFactory(Metric metric, ConnectionFactory wrappedFactory) { - SslConnectionFactory connectionFactory = new SslConnectionFactory(createSslContextFactory(), wrappedFactory.getProtocol()); - connectionFactory.addBean(new SslHandshakeFailedListener(metric, connectorConfig.name(), connectorConfig.listenPort())); - return connectionFactory; + var fac = new SslConnectionFactory(createSslContextFactory(), wrappedFactory.getProtocol()); + fac.setDirectBuffersForDecryption(false); + fac.setDirectBuffersForDecryption(false); + fac.addBean(new SslHandshakeFailedListener(metric, connectorConfig.name(), connectorConfig.listenPort())); + return fac; } - private SslContextFactory createSslContextFactory() { + private SslContextFactory.Server createSslContextFactory() { DefaultConnectorSsl ssl = new DefaultConnectorSsl(); sslProvider.configureSsl(ssl, connectorConfig.name(), connectorConfig.listenPort()); return ssl.createSslContextFactory(); diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java index ac50cbbb518..342d7ab9c4a 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HealthCheckProxyHandler.java @@ -4,14 +4,24 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.concurrent.DaemonThreadFactory; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.security.SslContextBuilder; +import com.yahoo.security.TrustAllX509TrustManager; import com.yahoo.security.tls.TransportSecurityOptions; import com.yahoo.security.tls.TransportSecurityUtils; -import com.yahoo.security.TrustAllX509TrustManager; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.server.DetectorConnectionFactory; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.SslConnectionFactory; @@ -19,14 +29,6 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.util.ssl.SslContextFactory; import javax.net.ssl.SSLContext; -import javax.servlet.AsyncContext; -import javax.servlet.AsyncEvent; -import javax.servlet.AsyncListener; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.Duration; import java.util.HashMap; @@ -89,7 +91,7 @@ class HealthCheckProxyHandler extends HandlerWrapper { Optional.ofNullable(targetConnector.getConnectionFactory(SslConnectionFactory.class)) .or(() -> Optional.ofNullable(targetConnector.getConnectionFactory(DetectorConnectionFactory.class)) .map(detectorConnFactory -> detectorConnFactory.getBean(SslConnectionFactory.class))) - .map(connFactory -> (SslContextFactory.Server) connFactory.getSslContextFactory()) + .map(SslConnectionFactory::getSslContextFactory) .orElseThrow(() -> new IllegalArgumentException("Health check proxy can only target https port")); boolean proxyProtocol = targetConnector.connectorConfig().proxyProtocol().enabled(); return new ProxyTarget(targetPort, clientTimeout,handlerTimeout, cacheExpiry, sslContextFactory, proxyProtocol); @@ -269,13 +271,14 @@ class HealthCheckProxyHandler extends HandlerWrapper { synchronized (this) { if (client == null) { int timeoutMillis = (int) clientTimeout.toMillis(); - SslContextFactory.Client clientSsl = new SslContextFactory.Client(); + var clientSsl = new SslContextFactory.Client(); clientSsl.setHostnameVerifier((__, ___) -> true); clientSsl.setSslContext(getSslContext(serverSsl)); - HttpClient client = new HttpClient(clientSsl); + var connector = new ClientConnector(); + connector.setSslContextFactory(clientSsl); + HttpClient client = new HttpClient(new HttpClientTransportOverHTTP(connector)); client.setMaxConnectionsPerDestination(4); client.setConnectTimeout(timeoutMillis); - client.setStopTimeout(timeoutMillis); client.setIdleTimeout(timeoutMillis); client.setUserAgentField(new HttpField(HttpHeader.USER_AGENT, "health-check-proxy-client")); client.start(); diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java index 9292e2024df..b4c933c1168 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java @@ -12,6 +12,11 @@ import com.yahoo.jdisc.handler.RequestHandler; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.HttpHeaders; import com.yahoo.jdisc.http.HttpRequest; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.server.HTTP2ServerConnection; import org.eclipse.jetty.io.Connection; @@ -20,11 +25,6 @@ import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.Callback; -import javax.servlet.AsyncContext; -import javax.servlet.AsyncEvent; -import javax.servlet.AsyncListener; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Instant; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java index 8a298fb3268..d45a8789e4c 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java @@ -3,10 +3,10 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.jdisc.service.CurrentContainer; +import jakarta.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.Utf8Appendable; -import javax.servlet.http.HttpServletRequest; import java.net.InetSocketAddress; import java.net.URI; import java.security.cert.X509Certificate; @@ -94,6 +94,6 @@ class HttpRequestFactory { } private static X509Certificate[] getCertChain(HttpServletRequest servletRequest) { - return (X509Certificate[]) servletRequest.getAttribute("javax.servlet.request.X509Certificate"); + return (X509Certificate[]) servletRequest.getAttribute(RequestUtils.SERVLET_REQUEST_X509CERT); } } diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java index 3fb81cb5352..81789881b68 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java @@ -4,6 +4,11 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.jdisc.http.ServerConfig; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.AsyncContextEvent; @@ -11,14 +16,8 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.component.Graceful; -import javax.servlet.AsyncEvent; -import javax.servlet.AsyncListener; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -27,12 +26,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; import java.util.function.ObjLongConsumer; import java.util.stream.Collectors; @@ -49,7 +46,7 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful static final String requestTypeAttribute = "requestType"; - private final AtomicReference<FutureCallback> shutdown = new AtomicReference<>(); + private final Shutdown shutdown; private final List<String> monitoringHandlerPaths; private final List<String> searchHandlerPaths; private final Set<String> ignoredUserAgents; @@ -66,6 +63,10 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful this.monitoringHandlerPaths = monitoringHandlerPaths; this.searchHandlerPaths = searchHandlerPaths; this.ignoredUserAgents = Set.copyOf(ignoredUserAgents); + this.shutdown = new Shutdown(this) { + @Override public boolean isShutdownDone() { return inFlight.get() == 0; } + }; + } private final AsyncListener completionWatcher = new AsyncListener() { @@ -97,7 +98,7 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful try { Handler handler = getHandler(); - if (handler != null && shutdown.get() == null && isStarted()) { + if (handler != null && !shutdown.isShutdown() && isStarted()) { handler.handle(path, baseRequest, request, response); } else if ( ! baseRequest.isHandled()) { baseRequest.setHandled(true); @@ -129,14 +130,9 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful .increment()); } long live = inFlight.decrementAndGet(); - FutureCallback shutdownCb = shutdown.get(); - if (shutdownCb != null) { - if (flushableResponse != null) { - flushableResponse.flushBuffer(); - } - if (live == 0) { - shutdownCb.succeeded(); - } + if (shutdown.isShutdown()) { + if (flushableResponse != null) flushableResponse.flushBuffer(); + if (live == 0) shutdown.check(); } } @@ -162,35 +158,19 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful @Override protected void doStart() throws Exception { - shutdown.set(null); + shutdown.cancel(); super.doStart(); } @Override protected void doStop() throws Exception { + shutdown.cancel(); super.doStop(); - FutureCallback shutdownCb = shutdown.get(); - if ( ! shutdownCb.isDone()) { - shutdownCb.failed(new TimeoutException()); - } } - @Override - public Future<Void> shutdown() { - FutureCallback shutdownCb = new FutureCallback(false); - shutdown.compareAndSet(null, shutdownCb); - shutdownCb = shutdown.get(); - if (inFlight.get() == 0) { - shutdownCb.succeeded(); - } - return shutdownCb; - } + @Override public CompletableFuture<Void> shutdown() { return shutdown.shutdown(); } + @Override public boolean isShutdown() { return shutdown.isShutdown(); } - @Override - public boolean isShutdown() { - FutureCallback futureCallback = shutdown.get(); - return futureCallback != null && futureCallback.isDone(); - } static class Dimensions { final String protocol; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java index 4b4aff0a9bd..bd052f14867 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java @@ -5,13 +5,13 @@ import com.yahoo.container.logging.AccessLogEntry; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.handler.OverloadException; import com.yahoo.jdisc.http.HttpRequest.Method; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; import java.util.Map; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java index b3069a64821..b17877cee84 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java @@ -3,16 +3,13 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; -import org.eclipse.jetty.http.HttpCompliance; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.eclipse.jetty.io.ConnectionStatistics; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; -import java.net.Socket; -import java.net.SocketException; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -26,8 +23,6 @@ class JDiscServerConnector extends ServerConnector { private final Metric.Context metricCtx; private final ConnectionStatistics statistics; private final ConnectorConfig config; - private final boolean tcpKeepAlive; - private final boolean tcpNoDelay; private final Metric metric; private final String connectorName; private final int listenPort; @@ -36,14 +31,13 @@ class JDiscServerConnector extends ServerConnector { ConnectionMetricAggregator connectionMetricAggregator, ConnectionFactory... factories) { super(server, factories); this.config = config; - this.tcpKeepAlive = config.tcpKeepAliveEnabled(); - this.tcpNoDelay = config.tcpNoDelay(); this.metric = metric; this.connectorName = config.name(); this.listenPort = config.listenPort(); this.metricCtx = metric.createContext(createConnectorDimensions(listenPort, connectorName, 0)); this.statistics = new ConnectionStatistics(); + setAcceptedTcpNoDelay(config.tcpNoDelay()); addBean(statistics); ConnectorConfig.Throttling throttlingConfig = config.throttling(); if (throttlingConfig.enabled()) { @@ -56,17 +50,6 @@ class JDiscServerConnector extends ServerConnector { setAcceptQueueSize(config.acceptQueueSize()); setReuseAddress(config.reuseAddress()); setIdleTimeout((long) (config.idleTimeout() * 1000)); - addBean(HttpCompliance.RFC7230); - } - - @Override - protected void configure(final Socket socket) { - super.configure(socket); - try { - socket.setKeepAlive(tcpKeepAlive); - socket.setTcpNoDelay(tcpNoDelay); - } catch (SocketException ignored) { - } } public ConnectionStatistics getStatistics() { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java index 53d8698c417..6406125dcc3 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java @@ -30,6 +30,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.StandardConstants; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; @@ -113,7 +114,7 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List info.setProxyProtocolVersion("v2"); } if (connection.getEndPoint() instanceof ProxyConnectionFactory.ProxyEndPoint) { - InetSocketAddress remoteAddress = connection.getEndPoint().getRemoteAddress(); + var remoteAddress = connection.getEndPoint().getRemoteSocketAddress(); info.setRemoteAddress(remoteAddress); } }); @@ -243,7 +244,7 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List private long httpBytesSent = 0; private long requests = 0; private long responses = 0; - private InetSocketAddress remoteAddress; + private SocketAddress remoteAddress; private byte[] sslSessionId; private String sslProtocol; private String sslCipherSuite; @@ -290,7 +291,7 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List synchronized ConnectionInfo incrementResponses() { ++this.responses; return this; } - synchronized ConnectionInfo setRemoteAddress(InetSocketAddress remoteAddress) { + synchronized ConnectionInfo setRemoteAddress(SocketAddress remoteAddress) { this.remoteAddress = remoteAddress; return this; } @@ -354,9 +355,9 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List builder.withLocalAddress(localAddress.getHostString()) .withLocalPort(localAddress.getPort()); } - if (remoteAddress != null) { - builder.withRemoteAddress(remoteAddress.getHostString()) - .withRemotePort(remoteAddress.getPort()); + if (remoteAddress instanceof InetSocketAddress isa) { + builder.withRemoteAddress(isa.getHostString()) + .withRemotePort(isa.getPort()); } if (sslProtocol != null && sslCipherSuite != null && sslSessionId != null) { builder.withSslProtocol(sslProtocol) diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java index 775c903f5f8..7b723b3a48e 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java @@ -27,8 +27,6 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.server.handler.gzip.GzipHttpOutputInterceptor; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.log.JavaUtilLog; -import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.thread.QueuedThreadPool; import javax.management.remote.JMXServiceURL; @@ -70,8 +68,6 @@ public class JettyHttpServer extends AbstractServerProvider { if (connectorFactories.allComponents().isEmpty()) throw new IllegalArgumentException("No connectors configured."); - initializeJettyLogging(); - server = new Server(); server.setStopTimeout((long)(serverConfig.stopTimeout() * 1000.0)); server.setRequestLog(new AccessLogRequestLog(requestLog, serverConfig.accessLog())); @@ -96,15 +92,6 @@ public class JettyHttpServer extends AbstractServerProvider { this.metricsReporter = new ServerMetricReporter(metric, server); } - private static void initializeJettyLogging() { - // Note: Jetty is logging stderr if no logger is explicitly configured - try { - Log.setLog(new JavaUtilLog()); - } catch (Exception e) { - throw new RuntimeException("Unable to initialize logging framework for Jetty"); - } - } - private static void setupJmx(Server server, ServerConfig serverConfig) { if (serverConfig.jmx().enabled()) { System.setProperty("java.rmi.server.hostname", "localhost"); @@ -152,7 +139,7 @@ public class JettyHttpServer extends AbstractServerProvider { } StatisticsHandler root = newGenericStatisticsHandler(); addChainToRoot(root, List.of( - newResponseStatisticsHandler(serverCfg), newGzipHandler(serverCfg), perConnectorHandlers)); + newResponseStatisticsHandler(serverCfg), newGzipHandler(), perConnectorHandlers)); return root; } @@ -253,22 +240,18 @@ public class JettyHttpServer extends AbstractServerProvider { return statisticsHandler; } - private static GzipHandler newGzipHandler(ServerConfig serverConfig) { - GzipHandler gzipHandler = new GzipHandlerWithVaryHeaderFixed(); - gzipHandler.setCompressionLevel(serverConfig.responseCompressionLevel()); - gzipHandler.setInflateBufferSize(8 * 1024); - gzipHandler.setIncludedMethods("GET", "POST", "PUT", "PATCH"); - return gzipHandler; - } + private static GzipHandler newGzipHandler() { return new GzipHandlerWithVaryHeaderFixed(); } /** A subclass which overrides Jetty's default behavior of including user-agent in the vary field */ private static class GzipHandlerWithVaryHeaderFixed extends GzipHandler { - @Override - public HttpField getVaryField() { - return GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING; + GzipHandlerWithVaryHeaderFixed() { + setInflateBufferSize(8 * 1024); + setIncludedMethods("GET", "POST", "PUT", "PATCH"); } + @Override public HttpField getVaryField() { return GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING; } + } } diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java index 1bc862bc787..da4de957739 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java @@ -1,12 +1,12 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.server.jetty; +import jakarta.servlet.http.HttpServletRequest; import org.eclipse.jetty.http2.server.HTTP2ServerConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; - -import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.SecureRequestCustomizer; /** * @author bjorncs @@ -15,7 +15,7 @@ public class RequestUtils { public static final String JDISC_REQUEST_X509CERT = "jdisc.request.X509Certificate"; public static final String JDISC_REQUEST_CHAIN = "jdisc.request.chain"; public static final String JDISC_RESPONSE_CHAIN = "jdisc.response.chain"; - public static final String SERVLET_REQUEST_X509CERT = "javax.servlet.request.X509Certificate"; + public static final String SERVLET_REQUEST_X509CERT = SecureRequestCustomizer.JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE; // The local port as reported by servlet spec. This will be influenced by Host header and similar mechanisms. // The request URI uses the local listen port as the URI is used for handler routing/bindings. diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java index 4b66715fcf7..d853282a5f5 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletOutputStreamWriter.java @@ -2,9 +2,9 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.handler.CompletionHandler; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java index 3703878f595..2f2c48e0b48 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletRequestReader.java @@ -3,10 +3,10 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.handler.CompletionHandler; import com.yahoo.jdisc.handler.ContentChannel; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Objects; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java index e90dde0e4eb..6afb55f5b13 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ServletResponseController.java @@ -9,10 +9,10 @@ import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.HttpHeaders; import com.yahoo.jdisc.http.HttpResponse; import com.yahoo.jdisc.service.BindingSetNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.MimeTypes; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java index b420aabc598..96f0cdebd62 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java @@ -3,13 +3,13 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.ConnectorConfig; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; -import javax.servlet.DispatcherType; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** diff --git a/container-core/src/main/java/com/yahoo/metrics/ContainerMetrics.java b/container-core/src/main/java/com/yahoo/metrics/ContainerMetrics.java index 01108ff36e9..5c2f1c1be79 100644 --- a/container-core/src/main/java/com/yahoo/metrics/ContainerMetrics.java +++ b/container-core/src/main/java/com/yahoo/metrics/ContainerMetrics.java @@ -62,7 +62,50 @@ public enum ContainerMetrics { MEM_DIRECT_COUNT("mem.direct.count", Unit.BYTE, "Number of direct memory allocations"), MEM_NATIVE_TOTAL("mem.native.total", Unit.BYTE, "Total available native memory"), MEM_NATIVE_FREE("mem.native.free", Unit.BYTE, "Currently free native memory"), - MEM_NATIVE_USED("mem.native.used", Unit.BYTE, "Native memory currently used"); + MEM_NATIVE_USED("mem.native.used", Unit.BYTE, "Native memory currently used"), + + + // SearchChain metrics + PEAK_QPS("peak_qps", Unit.OPERATION_PER_SECOND, "The highest number of qps for a second for this metrics shapshot"), + SEARCH_CONNECTIONS("search_connections", Unit.CONNECTION, "Number of search connections"), + FEED_LATENCY("feed.latency", Unit.MILLISECOND, "Feed latency"), + FEED_HTTP_REQUESTS("feed.http-requests", Unit.OPERATION, "Feed HTTP requests"), + QUERIES("queries", Unit.OPERATION, "Query volume"), + QUERY_CONTAINER_LATENCY("query_container_latency", Unit.MILLISECOND, "The query execution time consumed in the container"), + QUERY_LATENCY("query_latency", Unit.MILLISECOND, "The overall query latency as seen by the container"), + QUERY_TIMEOUT("query_timeout", Unit.MILLISECOND, "The amount of time allowed for query execytion, from the client"), + FAILED_QUERIES("failed_queries", Unit.OPERATION, "The number of failed queries"), + DEGRADED_QUERIES("degraded_queries", Unit.OPERATION, "The number of degraded queries, e.g. due to some conent nodes not responding in time"), + HITS_PER_QUERY("hits_per_query", Unit.HIT, "The number of hits returned"), + QUERY_HIT_OFFSET("query_hit_offset", Unit.HIT, "The offset for hits returned"), + DOCUMENTS_COVERED("documents_covered", Unit.DOCUMENT, "The combined number of documents considered during query evaluation"), + DOCUMENTS_TOTAL("documents_total", Unit.DOCUMENT, "The number of documents to be evaluated if all requests had been fully executed"), + DOCUMENTS_TARGET_TOTAL("documents_target_total", Unit.DOCUMENT, "The target number of total documents to be evaluated when when all data is in sync"), + JDISC_RENDER_LATENCY("jdisc.render.latency", Unit.MILLISECOND, "The time used by the container to render responses"), + QUERY_ITEM_COUNT("query_item_count", Unit.QUERY_ITEM, "The number of query items (terms, phrases, etc)"), + + TOTAL_HITS_PER_QUERY("totalhits_per_query", Unit.HIT, "The total number of documents found to match queries"), + EMPTY_RESULTS("empty_results", Unit.OPERATION, "Number of queries matching no documents"), + REQUESTS_OVER_QUOTA("requestsOverQuota", Unit.OPERATION, "The number of requests rejected due to exceeding quota"), + + RELEVANCE_AT_1("relevance.at_1", Unit.RELEVANCE, "The relevance of hit number 1"), + RELEVANCE_AT_3("relevance.at_3", Unit.RELEVANCE, "The relevance of hit number 3"), + RELEVANCE_AT_10("relevance.at_10", Unit.RELEVANCE, "The relevance of hit number 10"), + + // Errors from search container + ERROR_TIMEOUT("error.timeout", Unit.OPERATION, "Requests that timed out"), + ERROR_BACKENDS_OOS("error.backends_oos", Unit.OPERATION, "Requests that failed due to no available backends nodes"), + ERROR_PLUGIN_FAILURE("error.plugin_failure", Unit.OPERATION, "Requests that failed due to plugin failure"), + ERROR_BACKEND_COMMUNICATION_ERROR("error.backend_communication_error", Unit.OPERATION, "Requests that failed due to backend communication error"), + ERROR_EMPTY_DOCUMENT_SUMMARIES("error.empty_document_summaries", Unit.OPERATION, "Requests that failed due to missing document summaries"), + ERROR_INVALID_QUERY_PARAMETER("error.invalid_query_parameter", Unit.OPERATION, "Requests that failed due to invalid query parameters"), + ERROR_INTERNAL_SERVER_ERROR("error.internal_server_error", Unit.OPERATION, "Requests that failed due to internal server error"), + ERROR_MISCONFIGURED_SERVER("error.misconfigured_server", Unit.OPERATION, "Requests that failed due to misconfigured server"), + ERROR_INVALID_QUERY_TRANSFORMATION("error.invalid_query_transformation", Unit.OPERATION, "Requests that failed due to invalid query transformation"), + ERROR_RESULTS_WITH_ERRORS("error.results_with_errors", Unit.OPERATION, "The number of queries with error payload"), + ERROR_UNSPECIFIED("error.unspecified", Unit.OPERATION, "Requests that failed for an unspecified reason"), + ERROR_UNHANDLED_EXCEPTION("error.unhandled_exception", Unit.OPERATION, "Requests that failed due to an unhandled exception"); + private final String name; private final Unit unit; diff --git a/container-core/src/main/java/com/yahoo/metrics/Unit.java b/container-core/src/main/java/com/yahoo/metrics/Unit.java index d69b8446bd5..58960170325 100644 --- a/container-core/src/main/java/com/yahoo/metrics/Unit.java +++ b/container-core/src/main/java/com/yahoo/metrics/Unit.java @@ -17,6 +17,8 @@ public enum Unit { OPERATION_PER_SECOND(BaseUnit.OPERATION, BaseUnit.SECOND), QUERY(BaseUnit.QUERY), QUERY_PER_SECOND(BaseUnit.QUERY, BaseUnit.SECOND), + QUERY_ITEM(BaseUnit.QUERY_ITEM), + RELEVANCE(BaseUnit.RELEVANCE), REQUEST(BaseUnit.REQUEST), RESPONSE(BaseUnit.RESPONSE), RESPONSE_PER_SECOND(BaseUnit.RESPONSE, BaseUnit.SECOND), @@ -58,6 +60,8 @@ public enum Unit { MILLISECOND("millisecond", "ms"), OPERATION("operation"), QUERY("query"), + QUERY_ITEM("query-item"), + RELEVANCE("relevance"), REQUEST("request"), RESPONSE("response"), SECOND("second", "s"), diff --git a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def index ecbc451ead1..bdcc3f9e40a 100644 --- a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def +++ b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def @@ -28,7 +28,8 @@ reuseAddress bool default=true # The maximum idle time for a connection, which roughly translates to the Socket.setSoTimeout(int). idleTimeout double default=180.0 -# Whether or not to have socket keep alive turned on. +# TODO Vespa 9 Remove +# Has no effect since Jetty 11 upgrade tcpKeepAliveEnabled bool default=false # Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm). diff --git a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def index f34fd523207..c15cb6b2cc4 100644 --- a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def +++ b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.server.def @@ -4,7 +4,8 @@ namespace=jdisc.http # Whether to enable developer mode, where stack traces etc are visible in response bodies. developerMode bool default=false -# The gzip compression level to use, if compression is enabled in a request. +# TODO Vespa 9 Remove +# Has no effect since Jetty 11 upgrade responseCompressionLevel int default=6 # Whether the request body of POSTed forms should be removed (form parameters are available as request parameters). diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java index 1ff2783cc53..ce205b1a893 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java @@ -5,6 +5,8 @@ import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ServerConfig; import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -12,8 +14,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java index 8b18c8cf09d..fdb9f2226de 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ErrorResponseContentCreatorTest.java @@ -2,9 +2,9 @@ package com.yahoo.jdisc.http.server.jetty; +import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java index a23a3505bcb..e4b82db5b9f 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactoryTest.java @@ -8,15 +8,17 @@ import com.yahoo.jdisc.Response; import com.yahoo.jdisc.handler.RequestHandler; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.jdisc.service.CurrentContainer; +import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; import java.net.URI; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Steinar Knutsen diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java index 165659389ec..502702ccf35 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java @@ -2,6 +2,9 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.http.server.jetty.HttpResponseStatisticsCollector.StatisticsEntry; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; @@ -10,6 +13,7 @@ import org.eclipse.jetty.http.MetaData.Response; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpChannelOverHttp; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.server.Request; @@ -19,9 +23,6 @@ import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; @@ -164,8 +165,8 @@ public class HttpResponseStatisticsCollectorTest { } private Request testRequest(String scheme, int responseCode, String httpMethod, String path, com.yahoo.jdisc.Request.RequestType explicitRequestType) { - HttpChannel channel = new HttpChannel(connector, new HttpConfiguration(), null, new DummyTransport()); - MetaData.Request metaData = new MetaData.Request(httpMethod, new HttpURI(scheme + "://" + path), HttpVersion.HTTP_1_1, new HttpFields()); + HttpChannel channel = new HttpChannelOverHttp(null, connector, new HttpConfiguration(), null, new DummyTransport()); + MetaData.Request metaData = new MetaData.Request(httpMethod, HttpURI.build(scheme + "://" + path), HttpVersion.HTTP_1_1, HttpFields.build()); Request req = channel.getRequest(); if (explicitRequestType != null) req.setAttribute("requestType", explicitRequestType); @@ -192,7 +193,7 @@ public class HttpResponseStatisticsCollectorTest { private final class DummyTransport implements HttpTransport { @Override - public void send(Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback) { + public void send(MetaData.Request request, Response response, ByteBuffer byteBuffer, boolean b, Callback callback) { callback.succeeded(); } @@ -202,11 +203,6 @@ public class HttpResponseStatisticsCollectorTest { } @Override - public boolean isOptimizedForDirectBuffers() { - return false; - } - - @Override public void push(MetaData.Request request) { } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java index 7cce9f2a9ff..ae1a6494acd 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerConformanceTest.java @@ -807,6 +807,7 @@ public class HttpServerConformanceTest extends ServerProviderConformanceTest { post.setProtocolVersion(HttpVersion.HTTP_1_1); request = post; } + request.addHeader("Connection", "close"); return executorService.submit(() -> httpClient.execute(request)); } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java index 318067ac634..39b6dcdc6d5 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java @@ -186,9 +186,10 @@ public class HttpServerTest { @Test void requireThatServerCanEchoCompressed() throws Exception { final JettyTestDriver driver = JettyTestDriver.newInstance(new EchoRequestHandler()); - SimpleHttpClient client = driver.newClient(true); - client.get("/status.html") - .expectStatusCode(is(OK)); + try (SimpleHttpClient client = driver.newClient(true)) { + client.get("/status.html") + .expectStatusCode(is(OK)); + } assertTrue(driver.close()); } @@ -532,9 +533,9 @@ public class HttpServerTest { .withTrustStore(certificateFile) .build(); - new SimpleHttpClient(trustStoreOnlyCtx, driver.server().getListenPort(), false) - .get("/dummy.html") - .expectStatusCode(is(UNAUTHORIZED)); + try (var c = new SimpleHttpClient(trustStoreOnlyCtx, driver.server().getListenPort(), false)) { + c.get("/dummy.html").expectStatusCode(is(UNAUTHORIZED)); + } assertTrue(driver.close()); } @@ -550,9 +551,9 @@ public class HttpServerTest { .withTrustStore(certificateFile) .build(); - new SimpleHttpClient(trustStoreOnlyCtx, driver.server().getListenPort(), false) - .get("/status.html") - .expectStatusCode(is(OK)); + try (var c = new SimpleHttpClient(trustStoreOnlyCtx, driver.server().getListenPort(), false)) { + c.get("/status.html").expectStatusCode(is(OK)); + } assertTrue(driver.close()); } diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ProxyProtocolTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ProxyProtocolTest.java index d4d6dcee957..6cd6f05933a 100644 --- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ProxyProtocolTest.java +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/ProxyProtocolTest.java @@ -12,6 +12,8 @@ import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.assertj.core.api.Assertions; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -185,14 +187,15 @@ class ProxyProtocolTest { // Using Jetty's http client as Apache httpclient does not support the proxy-protocol v1/v2. private static HttpClient createJettyHttpClient(Path certificateFile) throws Exception { - SslContextFactory.Client clientSslCtxFactory = new SslContextFactory.Client(); - clientSslCtxFactory.setHostnameVerifier(NoopHostnameVerifier.INSTANCE); - clientSslCtxFactory.setSslContext(new SslContextBuilder().withTrustStore(certificateFile).build()); - - HttpClient client = new HttpClient(clientSslCtxFactory); - client.setConnectTimeout(60*1000); - client.setStopTimeout(60*1000); - client.setIdleTimeout(60*1000); + var ssl = new SslContextFactory.Client(); + ssl.setHostnameVerifier(NoopHostnameVerifier.INSTANCE); + ssl.setSslContext(new SslContextBuilder().withTrustStore(certificateFile).build()); + var connector = new ClientConnector(); + connector.setSslContextFactory(ssl); + HttpClient client = new HttpClient(new HttpClientTransportOverHTTP(connector)); + int timeout = 60 * 1000; + client.setConnectTimeout(timeout); + client.setIdleTimeout(timeout); client.start(); return client; } diff --git a/container-dev/pom.xml b/container-dev/pom.xml index f62cc69a428..c327b73d953 100644 --- a/container-dev/pom.xml +++ b/container-dev/pom.xml @@ -105,8 +105,8 @@ <!-- START JETTY embedded jars --> <exclusion> - <groupId>org.eclipse.jetty.alpn</groupId> - <artifactId>alpn-api</artifactId> + <groupId>org.eclipse.jetty.http2</groupId> + <artifactId>http2-common</artifactId> </exclusion> <exclusion> <groupId>org.eclipse.jetty.http2</groupId> @@ -118,11 +118,19 @@ </exclusion> <exclusion> <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-alpn-server</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-client</artifactId> </exclusion> <exclusion> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-continuation</artifactId> + <artifactId>jetty-http</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-io</artifactId> </exclusion> <exclusion> <groupId>org.eclipse.jetty</groupId> @@ -138,7 +146,11 @@ </exclusion> <exclusion> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlets</artifactId> + <artifactId>jetty-util</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>jetty-jakarta-servlet-api</artifactId> </exclusion> <!-- END JETTY embedded jars --> </exclusions> diff --git a/container-test/pom.xml b/container-test/pom.xml index d2806bb0330..d4da20cf9fe 100644 --- a/container-test/pom.xml +++ b/container-test/pom.xml @@ -116,8 +116,8 @@ <!-- START JETTY embedded jars --> <dependency> - <groupId>org.eclipse.jetty.alpn</groupId> - <artifactId>alpn-api</artifactId> + <groupId>org.eclipse.jetty.http2</groupId> + <artifactId>http2-common</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty.http2</groupId> @@ -129,11 +129,19 @@ </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-alpn-server</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-client</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-continuation</artifactId> + <artifactId>jetty-http</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-io</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> @@ -149,7 +157,11 @@ </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlets</artifactId> + <artifactId>jetty-util</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>jetty-jakarta-servlet-api</artifactId> </dependency> <!-- END JETTY embedded jars --> </dependencies> diff --git a/fat-model-dependencies/pom.xml b/fat-model-dependencies/pom.xml index 7d00f9ba58e..bbac77d9c0b 100644 --- a/fat-model-dependencies/pom.xml +++ b/fat-model-dependencies/pom.xml @@ -152,6 +152,10 @@ <groupId>org.eclipse.jetty.http2</groupId> <artifactId>*</artifactId> </exclusion> + <exclusion> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>*</artifactId> + </exclusion> </exclusions> </dependency> <dependency> diff --git a/fileacquirer/abi-spec.json b/fileacquirer/abi-spec.json index 560d1fb7a16..63e3ba71f2d 100644 --- a/fileacquirer/abi-spec.json +++ b/fileacquirer/abi-spec.json @@ -25,17 +25,6 @@ ], "fields" : [ ] }, - "com.yahoo.filedistribution.fileacquirer.FileReferenceDoesNotExistException" : { - "superClass" : "java.lang.RuntimeException", - "interfaces" : [ ], - "attributes" : [ - "public" - ], - "methods" : [ ], - "fields" : [ - "public final java.lang.String fileReference" - ] - }, "com.yahoo.filedistribution.fileacquirer.MockFileAcquirer" : { "superClass" : "java.lang.Object", "interfaces" : [ diff --git a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirer.java b/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirer.java index 03650963f96..3f25273334a 100644 --- a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirer.java +++ b/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirer.java @@ -2,6 +2,7 @@ package com.yahoo.filedistribution.fileacquirer; import com.yahoo.config.FileReference; +import com.yahoo.vespa.config.FileReferenceDoesNotExistException; import java.io.File; import java.util.concurrent.TimeUnit; diff --git a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirerImpl.java b/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirerImpl.java index 5d6477cd927..9fe876d358a 100644 --- a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirerImpl.java +++ b/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/FileAcquirerImpl.java @@ -2,18 +2,23 @@ package com.yahoo.filedistribution.fileacquirer; import com.yahoo.cloud.config.filedistribution.FiledistributorrpcConfig; -import com.yahoo.config.subscription.ConfigSubscriber; import com.yahoo.config.FileReference; -import com.yahoo.jrt.*; - +import com.yahoo.config.subscription.ConfigSubscriber; +import com.yahoo.jrt.ErrorCode; +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.vespa.config.FileReferenceDoesNotExistException; +import java.io.File; import java.time.Duration; -import java.util.logging.Level; - -import java.util.logging.Logger; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.TimeUnit; -import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Retrieves the path to a file or directory on the local file system diff --git a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/MockFileAcquirer.java b/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/MockFileAcquirer.java index 744c49629e9..e393cf7bc4e 100644 --- a/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/MockFileAcquirer.java +++ b/fileacquirer/src/main/java/com/yahoo/filedistribution/fileacquirer/MockFileAcquirer.java @@ -2,6 +2,7 @@ package com.yahoo.filedistribution.fileacquirer; import com.yahoo.config.FileReference; +import com.yahoo.vespa.config.FileReferenceDoesNotExistException; import java.io.File; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/fileacquirer/src/test/java/MockFileAcquirerTest.java b/fileacquirer/src/test/java/MockFileAcquirerTest.java index 9a505118c31..dc8908249e0 100644 --- a/fileacquirer/src/test/java/MockFileAcquirerTest.java +++ b/fileacquirer/src/test/java/MockFileAcquirerTest.java @@ -1,11 +1,11 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + import com.yahoo.config.FileReference; import com.yahoo.filedistribution.fileacquirer.FileAcquirer; -import com.yahoo.filedistribution.fileacquirer.FileReferenceDoesNotExistException; import com.yahoo.filedistribution.fileacquirer.MockFileAcquirer; import com.yahoo.filedistribution.fileacquirer.TimeoutException; +import com.yahoo.vespa.config.FileReferenceDoesNotExistException; import org.junit.Test; - import java.io.File; import java.lang.reflect.Constructor; import java.util.HashMap; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java index 742b39e97c1..246a02aa441 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java @@ -9,13 +9,17 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.node.ClusterId; +import com.yahoo.vespa.hosted.provision.node.IP; +import java.util.ArrayList; import java.util.Comparator; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,13 +34,22 @@ import static java.util.stream.Collectors.collectingAndThen; */ public class NodeList extends AbstractFilteringList<Node, NodeList> { + private static final NodeList EMPTY = new NodeList(List.of(), false); + + /** + * A lazily populated cache of parent-child relationships. This exists to improve the speed of parent<->child + * lookup which is a frequent operation + */ + private final AtomicReference<Map<String, NodeFamily>> nodeCache = new AtomicReference<>(null); + private final AtomicReference<Set<String>> ipCache = new AtomicReference<>(null); + protected NodeList(List<Node> nodes, boolean negate) { super(nodes, negate, NodeList::new); } /** Returns the node with the given hostname from this list, or empty if it is not present */ public Optional<Node> node(String hostname) { - return matching(node -> node.hostname().equals(hostname)).first(); + return get(hostname).map(NodeFamily::node); } /** Returns the subset of nodes which are retired */ @@ -189,7 +202,9 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { /** Returns the child nodes of the given parent node */ public NodeList childrenOf(String hostname) { - return matching(node -> node.hasParent(hostname)); + NodeList children = get(hostname).map(NodeFamily::children).map(NodeList::copyOf).orElse(EMPTY); + // Fallback, in case the parent itself is not in this list + return children.isEmpty() ? matching(node -> node.hasParent(hostname)) : children; } public NodeList childrenOf(Node parent) { @@ -221,24 +236,21 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { public NodeList parentsOf(NodeList children) { return children.stream() .map(this::parentOf) - .filter(Optional::isPresent) .flatMap(Optional::stream) .collect(collectingAndThen(Collectors.toList(), NodeList::copyOf)); } + /** Returns the parent node of the given child node */ + public Optional<Node> parentOf(Node child) { + return child.parentHostname().flatMap(this::get).map(NodeFamily::node); + } + /** Returns the nodes contained in the group identified by given index */ public NodeList group(int index) { return matching(n -> n.allocation().isPresent() && n.allocation().get().membership().cluster().group().equals(Optional.of(ClusterSpec.Group.from(index)))); } - /** Returns the parent node of the given child node */ - public Optional<Node> parentOf(Node child) { - return child.parentHostname() - .flatMap(parentHostname -> stream().filter(node -> node.hostname().equals(parentHostname)) - .findFirst()); - } - /** Returns the hostnames of nodes in this */ public Set<String> hostnames() { return stream().map(Node::hostname).collect(Collectors.toUnmodifiableSet()); @@ -316,6 +328,31 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { }); } + /** + * Returns the number of unused IP addresses in the pool, assuming any and all unaccounted for hostnames + * in the pool are resolved to exactly 1 IP address (or 2 with {@link IP.IpAddresses.Protocol#dualStack}). + */ + public int eventuallyUnusedIpAddressCount(Node host) { + // The count in this method relies on the size of the IP address pool if that's non-empty, + // otherwise fall back to the address/hostname pool. + if (host.ipConfig().pool().ipSet().isEmpty()) { + Set<String> allHostnames = cache().keySet(); + return (int) host.ipConfig().pool().getAddressList().stream() + .filter(address -> !allHostnames.contains(address.hostname())) + .count(); + } + Set<String> allIps = ipCache.updateAndGet((old) -> { + if (old != null) { + return old; + } + return stream().flatMap(node -> node.ipConfig().primary().stream()) + .collect(Collectors.toUnmodifiableSet()); + }); + return (int) host.ipConfig().pool().ipSet().stream() + .filter(address -> !allIps.contains(address)) + .count(); + } + private void ensureSingleCluster() { if (isEmpty()) return; @@ -336,6 +373,7 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { } public static NodeList copyOf(List<Node> nodes) { + if (nodes.isEmpty()) return EMPTY; return new NodeList(nodes, false); } @@ -354,4 +392,36 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { return this.asList().equals(((NodeList) other).asList()); } + /** Get node family, by given hostname */ + private Optional<NodeFamily> get(String hostname) { + return Optional.ofNullable(cache().get(hostname)); + } + + private Map<String, NodeFamily> cache() { + return nodeCache.updateAndGet((oldValue) -> { + if (oldValue != null) { + return oldValue; + } + Map<String, NodeFamily> newValue = new HashMap<>(); + for (var node : this) { + NodeFamily family; + if (node.parentHostname().isEmpty()) { + family = new NodeFamily(node, new ArrayList<>()); + for (var child : this) { + if (child.hasParent(node.hostname())) { + family.children.add(child); + } + } + } else { + family = new NodeFamily(node, List.of()); + } + newValue.put(node.hostname(), family); + } + return newValue; + }); + } + + /** A node and its children, if any */ + private record NodeFamily(Node node, List<Node> children) {} + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodesAndHosts.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodesAndHosts.java deleted file mode 100644 index 6e7fc340231..00000000000 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodesAndHosts.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.provision; - -import com.yahoo.vespa.hosted.provision.node.IP; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Wraps a NodeList and builds a host to children mapping for faster access - * as that is done very frequently. - * - * @author baldersheim - */ -public class NodesAndHosts<NL extends NodeList> { - private final NL nodes; - private final Map<String, HostAndNodes> host2Nodes = new HashMap<>(); - private final Set<String> allPrimaryIps = new HashSet<>(); - private final Set<String> allHostNames; - - public static <L extends NodeList> NodesAndHosts<L> create(L nodes) { - return new NodesAndHosts<L>(nodes); - } - - private NodesAndHosts(NL nodes) { - this.nodes = nodes; - nodes.forEach(node -> allPrimaryIps.addAll(node.ipConfig().primary())); - allHostNames = nodes.stream().map(Node::hostname).collect(Collectors.toSet()); - nodes.forEach(node -> { - node.parentHostname().ifPresentOrElse( - parent -> host2Nodes.computeIfAbsent(parent, key -> new HostAndNodes()).add(node), - () -> host2Nodes.computeIfAbsent(node.hostname(), key -> new HostAndNodes()).setHost(node)); - }); - - } - - /// Return the NodeList used for construction - public NL nodes() { return nodes; } - - public NodeList childrenOf(Node host) { - return childrenOf(host.hostname()); - } - public NodeList childrenOf(String hostname) { - HostAndNodes hostAndNodes = host2Nodes.get(hostname); - return hostAndNodes != null ? NodeList.copyOf(hostAndNodes.children) : NodeList.of(); - } - - public Optional<Node> parentOf(Node node) { - if (node.parentHostname().isEmpty()) return Optional.empty(); - - HostAndNodes hostAndNodes = host2Nodes.get(node.parentHostname().get()); - return hostAndNodes != null ? Optional.ofNullable(hostAndNodes.host) : Optional.empty(); - } - - /** - * Returns the number of unused IP addresses in the pool, assuming any and all unaccounted for hostnames - * in the pool are resolved to exactly 1 IP address (or 2 with {@link IP.IpAddresses.Protocol#dualStack}). - */ - public int eventuallyUnusedIpAddressCount(Node host) { - // The count in this method relies on the size of the IP address pool if that's non-empty, - // otherwise fall back to the address/hostname pool. - return (int) (host.ipConfig().pool().ipSet().isEmpty() - ? host.ipConfig().pool().getAddressList().stream().filter(address -> !allHostNames.contains(address.hostname())).count() - : host.ipConfig().pool().ipSet().stream().filter(address -> !allPrimaryIps.contains(address)).count()); - } - - private static class HostAndNodes { - private Node host; - private final List<Node> children; - HostAndNodes() { - this.host = null; - children = new ArrayList<>(); - } - void setHost(Node host) { this.host = host; } - void add(Node child) { children.add(child); } - } -} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java index 5769f978089..1d5581b511d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java @@ -22,7 +22,6 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeMutex; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.provisioning.HostProvisioner; @@ -31,6 +30,7 @@ import com.yahoo.vespa.hosted.provision.provisioning.NodeCandidate; import com.yahoo.vespa.hosted.provision.provisioning.NodePrioritizer; import com.yahoo.vespa.hosted.provision.provisioning.NodeSpec; import com.yahoo.vespa.hosted.provision.provisioning.ProvisionedHost; + import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -222,8 +222,8 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { ArrayList<Node> mutableNodes) { for (int clusterIndex = 0; clusterIndex < preprovisionCapacity.size(); ++clusterIndex) { ClusterCapacity clusterCapacity = preprovisionCapacity.get(clusterIndex); - NodesAndHosts<LockedNodeList> nodesAndHosts = NodesAndHosts.create(new LockedNodeList(mutableNodes, () -> {})); - List<Node> candidates = findCandidates(clusterCapacity, clusterIndex, nodesAndHosts); + LockedNodeList allNodes = new LockedNodeList(mutableNodes, () -> {}); + List<Node> candidates = findCandidates(clusterCapacity, clusterIndex, allNodes); int deficit = Math.max(0, clusterCapacity.count() - candidates.size()); if (deficit > 0) { return Optional.of(clusterCapacity.withCount(deficit)); @@ -236,7 +236,7 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { return Optional.empty(); } - private List<Node> findCandidates(ClusterCapacity clusterCapacity, int clusterIndex, NodesAndHosts<LockedNodeList> nodesAndHosts) { + private List<Node> findCandidates(ClusterCapacity clusterCapacity, int clusterIndex, LockedNodeList allNodes) { NodeResources nodeResources = toNodeResources(clusterCapacity); // We'll allocate each ClusterCapacity as a unique cluster in a dummy application @@ -249,7 +249,7 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { NodeSpec nodeSpec = NodeSpec.from(clusterCapacity.count(), nodeResources, false, true, nodeRepository().zone().cloud().account()); int wantedGroups = 1; - NodePrioritizer prioritizer = new NodePrioritizer(nodesAndHosts, applicationId, clusterSpec, nodeSpec, wantedGroups, + NodePrioritizer prioritizer = new NodePrioritizer(allNodes, applicationId, clusterSpec, nodeSpec, wantedGroups, true, nodeRepository().nameResolver(), nodeRepository().nodes(), nodeRepository().resourcesCalculator(), nodeRepository().spareCount(), nodeSpec.cloudAccount().isEnclave(nodeRepository().zone())); List<NodeCandidate> nodeCandidates = prioritizer.collect(List.of()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java index 552db84748d..fcc8e904a47 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java @@ -9,7 +9,6 @@ import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.provisioning.HostCapacity; import java.time.Duration; @@ -40,7 +39,7 @@ public abstract class NodeMover<MOVE> extends NodeRepositoryMaintainer { } /** Returns a suggested move for given node */ - protected abstract MOVE suggestedMove(Node node, Node fromHost, Node toHost, NodesAndHosts<? extends NodeList> allNodes); + protected abstract MOVE suggestedMove(Node node, Node fromHost, Node toHost, NodeList allNodes); private static class HostWithResources { private final Node node; @@ -56,17 +55,17 @@ public abstract class NodeMover<MOVE> extends NodeRepositoryMaintainer { } /** Find the best possible move */ - protected final MOVE findBestMove(NodesAndHosts<? extends NodeList> allNodes) { + protected final MOVE findBestMove(NodeList allNodes) { HostCapacity capacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); MOVE bestMove = emptyMove; // Shuffle nodes to not get stuck if the chosen move is consistently discarded. Node moves happen through // a soft request to retire (preferToRetire), which node allocation can disregard - NodeList activeNodes = allNodes.nodes().nodeType(NodeType.tenant) + NodeList activeNodes = allNodes.nodeType(NodeType.tenant) .state(Node.State.active) .shuffle(random); - Set<Node> spares = capacity.findSpareHosts(allNodes.nodes().asList(), nodeRepository().spareCount()); + Set<Node> spares = capacity.findSpareHosts(allNodes.asList(), nodeRepository().spareCount()); List<HostWithResources> hostResources = new ArrayList<>(); - allNodes.nodes().matching(nodeRepository().nodes()::canAllocateTenantNodeTo).forEach(host -> hostResources.add(new HostWithResources(host, capacity.availableCapacityOf(host)))); + allNodes.matching(nodeRepository().nodes()::canAllocateTenantNodeTo).forEach(host -> hostResources.add(new HostWithResources(host, capacity.availableCapacityOf(host)))); for (Node node : activeNodes) { if (node.parentHostname().isEmpty()) continue; ApplicationId applicationId = node.allocation().get().owner(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java index c853bfa2abe..3649f921480 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java @@ -8,7 +8,6 @@ import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.HostCapacity; @@ -42,15 +41,15 @@ public class Rebalancer extends NodeMover<Rebalancer.Move> { if (nodeRepository().zone().system().isCd()) return 1.0; // CD tests assert on # of nodes, avoid rebalnacing as it make tests unstable // Work with an unlocked snapshot as this can take a long time and full consistency is not needed - NodesAndHosts<NodeList> allNodes = NodesAndHosts.create(nodeRepository().nodes().list()); + NodeList allNodes = nodeRepository().nodes().list(); updateSkewMetric(allNodes); - if ( ! zoneIsStable(allNodes.nodes())) return 1.0; + if ( ! zoneIsStable(allNodes)) return 1.0; findBestMove(allNodes).execute(true, Agent.Rebalancer, deployer, metric, nodeRepository()); return 1.0; } @Override - protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodesAndHosts<? extends NodeList> allNodes) { + protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodeList allNodes) { HostCapacity capacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); double skewReductionAtFromHost = skewReductionByRemoving(node, fromHost, capacity); double skewReductionAtToHost = skewReductionByAdding(node, toHost, capacity); @@ -65,11 +64,11 @@ public class Rebalancer extends NodeMover<Rebalancer.Move> { } /** We do this here rather than in MetricsReporter because it is expensive and frequent updates are unnecessary */ - private void updateSkewMetric(NodesAndHosts<? extends NodeList> allNodes) { + private void updateSkewMetric(NodeList allNodes) { HostCapacity capacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); double totalSkew = 0; int hostCount = 0; - for (Node host : allNodes.nodes().nodeType(NodeType.host).state(Node.State.active)) { + for (Node host : allNodes.nodeType(NodeType.host).state(Node.State.active)) { hostCount++; totalSkew += Node.skew(host.flavor().resources(), capacity.unusedCapacityOf(host)); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java index d5b0c3baf19..5ce88346178 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java @@ -9,7 +9,6 @@ import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.maintenance.MaintenanceDeployment.Move; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.HostCapacity; @@ -117,13 +116,13 @@ public class SpareCapacityMaintainer extends NodeRepositoryMaintainer { if (nodeWhichCantMove.isEmpty()) return List.of(); Node node = nodeWhichCantMove.get(); - NodesAndHosts<NodeList> allNodes = NodesAndHosts.create(nodeRepository().nodes().list()); + NodeList allNodes = nodeRepository().nodes().list(); // Allocation will assign the spareCount most empty nodes as "spares", which will not be allocated on // unless needed for node failing. Our goal here is to make room on these spares for the given node HostCapacity hostCapacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); - Set<Node> spareHosts = hostCapacity.findSpareHosts(allNodes.nodes().hosts().satisfies(node.resources()).asList(), + Set<Node> spareHosts = hostCapacity.findSpareHosts(allNodes.hosts().satisfies(node.resources()).asList(), nodeRepository().spareCount()); - List<Node> hosts = allNodes.nodes().hosts().except(spareHosts).asList(); + List<Node> hosts = allNodes.hosts().except(spareHosts).asList(); CapacitySolver capacitySolver = new CapacitySolver(hostCapacity, maxIterations); List<Move> shortestMitigation = null; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java index f01e8ecd301..9f009b47983 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java @@ -8,7 +8,6 @@ import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.maintenance.MaintenanceDeployment.Move; import com.yahoo.vespa.hosted.provision.node.Agent; @@ -40,8 +39,8 @@ public class SwitchRebalancer extends NodeMover<Move> { protected double maintain() { if (!nodeRepository().nodes().isWorking()) return 0.0; if (!nodeRepository().zone().environment().isProduction()) return 1.0; - NodesAndHosts<NodeList> allNodes = NodesAndHosts.create(nodeRepository().nodes().list()); // Lockless as strong consistency is not needed - if (!zoneIsStable(allNodes.nodes())) return 1.0; + NodeList allNodes = nodeRepository().nodes().list(); // Lockless as strong consistency is not needed + if (!zoneIsStable(allNodes)) return 1.0; Move bestMove = findBestMove(allNodes); if (!bestMove.isEmpty()) { @@ -53,9 +52,9 @@ public class SwitchRebalancer extends NodeMover<Move> { } @Override - protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodesAndHosts<? extends NodeList> allNodes) { - NodeList clusterNodes = clusterOf(node, allNodes.nodes()); - NodeList clusterHosts = allNodes.nodes().parentsOf(clusterNodes); + protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodeList allNodes) { + NodeList clusterNodes = clusterOf(node, allNodes); + NodeList clusterHosts = allNodes.parentsOf(clusterNodes); if (onExclusiveSwitch(node, clusterHosts)) return Move.empty(); if (!increasesExclusiveSwitches(clusterNodes, clusterHosts, toHost)) return Move.empty(); return new Move(node, fromHost, toHost); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java index 46cc32c7156..07f9c439fe2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java @@ -11,7 +11,6 @@ import com.yahoo.transaction.Mutex; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.HostProvisioner.HostSharing; @@ -37,17 +36,8 @@ public class GroupPreparer { private final NodeRepository nodeRepository; private final Optional<HostProvisioner> hostProvisioner; - /** - * Contains list of prepared nodes and the NodesAndHost object to use for next prepare call. - */ - public static class PrepareResult { - public final List<Node> prepared; - public final NodesAndHosts<LockedNodeList> allNodesAndHosts; - PrepareResult(List<Node> prepared, NodesAndHosts<LockedNodeList> allNodesAndHosts) { - this.prepared = prepared; - this.allNodesAndHosts = allNodesAndHosts; - } - } + /** Contains list of prepared nodes and the {@link LockedNodeList} object to use for next prepare call */ + record PrepareResult(List<Node> prepared, LockedNodeList allNodes) {} public GroupPreparer(NodeRepository nodeRepository, Optional<HostProvisioner> hostProvisioner) { this.nodeRepository = nodeRepository; @@ -64,8 +54,8 @@ public class GroupPreparer { * This method will remove from this list if it finds it needs additional nodes * @param indices the next available node indices for this cluster. * This method will consume these when it allocates new nodes to the cluster. - * @param allNodesAndHosts list of all nodes and hosts. Use createNodesAndHostUnlocked to create param for - * first invocation. Then use previous PrepareResult.allNodesAndHosts for the following. + * @param allNodes list of all nodes and hosts. Use {@link #createUnlockedNodeList()} to create param for + * first invocation. Then use previous {@link PrepareResult#allNodes()} for the following. * @return the list of nodes this cluster group will have allocated if activated, and */ // Note: This operation may make persisted changes to the set of reserved and inactive nodes, @@ -73,37 +63,37 @@ public class GroupPreparer { // active config model which is changed on activate public PrepareResult prepare(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, NodeIndices indices, int wantedGroups, - NodesAndHosts<LockedNodeList> allNodesAndHosts) { + LockedNodeList allNodes) { log.log(Level.FINE, () -> "Preparing " + cluster.type().name() + " " + cluster.id() + " with requested resources " + requestedNodes.resources().orElse(NodeResources.unspecified())); // Try preparing in memory without global unallocated lock. Most of the time there should be no changes, // and we can return nodes previously allocated. NodeAllocation probeAllocation = prepareAllocation(application, cluster, requestedNodes, surplusActiveNodes, - indices::probeNext, wantedGroups, allNodesAndHosts); + indices::probeNext, wantedGroups, allNodes); if (probeAllocation.fulfilledAndNoChanges()) { List<Node> acceptedNodes = probeAllocation.finalNodes(); surplusActiveNodes.removeAll(acceptedNodes); indices.commitProbe(); - return new PrepareResult(acceptedNodes, allNodesAndHosts); + return new PrepareResult(acceptedNodes, allNodes); } else { // There were some changes, so re-do the allocation with locks indices.resetProbe(); List<Node> prepared = prepareWithLocks(application, cluster, requestedNodes, surplusActiveNodes, indices, wantedGroups); - return new PrepareResult(prepared, createNodesAndHostUnlocked()); + return new PrepareResult(prepared, createUnlockedNodeList()); } } - // Use this to create allNodesAndHosts param to prepare method for first invocation of prepare - public NodesAndHosts<LockedNodeList> createNodesAndHostUnlocked() { return NodesAndHosts.create(nodeRepository.nodes().list(PROBE_LOCK)); } + // Use this to create allNodes param to prepare method for first invocation of prepare + LockedNodeList createUnlockedNodeList() { return nodeRepository.nodes().list(PROBE_LOCK); } /// Note that this will write to the node repo. private List<Node> prepareWithLocks(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, NodeIndices indices, int wantedGroups) { try (Mutex lock = nodeRepository.applications().lock(application); Mutex allocationLock = nodeRepository.nodes().lockUnallocated()) { - NodesAndHosts<LockedNodeList> allNodesAndHosts = NodesAndHosts.create(nodeRepository.nodes().list(allocationLock)); + LockedNodeList allNodes = nodeRepository.nodes().list(allocationLock); NodeAllocation allocation = prepareAllocation(application, cluster, requestedNodes, surplusActiveNodes, - indices::next, wantedGroups, allNodesAndHosts); + indices::next, wantedGroups, allNodes); NodeType hostType = allocation.nodeType().hostType(); if (canProvisionDynamically(hostType) && allocation.hostDeficit().isPresent()) { HostSharing sharing = hostSharing(cluster, hostType); @@ -156,10 +146,10 @@ public class GroupPreparer { private NodeAllocation prepareAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, Supplier<Integer> nextIndex, int wantedGroups, - NodesAndHosts<LockedNodeList> allNodesAndHosts) { + LockedNodeList allNodes) { - NodeAllocation allocation = new NodeAllocation(allNodesAndHosts, application, cluster, requestedNodes, nextIndex, nodeRepository); - NodePrioritizer prioritizer = new NodePrioritizer(allNodesAndHosts, + NodeAllocation allocation = new NodeAllocation(allNodes, application, cluster, requestedNodes, nextIndex, nodeRepository); + NodePrioritizer prioritizer = new NodePrioritizer(allNodes, application, cluster, requestedNodes, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java index f29bf61149c..991bc22402d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java @@ -5,7 +5,6 @@ import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import java.util.ArrayList; import java.util.List; @@ -23,18 +22,15 @@ import java.util.stream.Collectors; */ public class HostCapacity { - private final NodesAndHosts<? extends NodeList> allNodes; + private final NodeList allNodes; private final HostResourcesCalculator hostResourcesCalculator; public HostCapacity(NodeList allNodes, HostResourcesCalculator hostResourcesCalculator) { - this(NodesAndHosts.create(Objects.requireNonNull(allNodes, "allNodes must be non-null")), hostResourcesCalculator); - } - public HostCapacity(NodesAndHosts<? extends NodeList> allNodes, HostResourcesCalculator hostResourcesCalculator) { this.allNodes = Objects.requireNonNull(allNodes, "allNodes must be non-null"); this.hostResourcesCalculator = Objects.requireNonNull(hostResourcesCalculator, "hostResourcesCalculator must be non-null"); } - public NodeList allNodes() { return allNodes.nodes(); } + public NodeList allNodes() { return allNodes; } /** * Spare hosts are the hosts in the system with the most free capacity. A zone may reserve a minimum number of spare @@ -97,9 +93,9 @@ public class HostCapacity { /** Returns the number of available IP addresses on given host */ int freeIps(Node host) { if (host.type() == NodeType.host) { - return (allNodes.eventuallyUnusedIpAddressCount(host)); + return allNodes.eventuallyUnusedIpAddressCount(host); } - return host.ipConfig().pool().findUnusedIpAddresses(allNodes.nodes()).size(); + return host.ipConfig().pool().findUnusedIpAddresses(allNodes).size(); } /** Returns the capacity of given host that is both free and usable */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index 63d5db09380..bf3ad5f15fb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -14,7 +14,6 @@ import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Allocation; @@ -43,7 +42,7 @@ class NodeAllocation { private static final Logger LOG = Logger.getLogger(NodeAllocation.class.getName()); /** List of all nodes in node-repository */ - private final NodesAndHosts<? extends NodeList> allNodesAndHosts; + private final NodeList allNodes; /** The application this list is for */ private final ApplicationId application; @@ -86,9 +85,9 @@ class NodeAllocation { private List<NodeCandidate> lastOffered; - NodeAllocation(NodesAndHosts<? extends NodeList> allNodesAndHosts, ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, + NodeAllocation(NodeList allNodes, ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, Supplier<Integer> nextIndex, NodeRepository nodeRepository) { - this.allNodesAndHosts = allNodesAndHosts; + this.allNodes = allNodes; this.application = application; this.cluster = cluster; this.requestedNodes = requestedNodes; @@ -214,7 +213,7 @@ class NodeAllocation { // In zones with shared hosts we require that if either of the nodes on the host requires exclusivity, // then all the nodes on the host must have the same owner - for (Node nodeOnHost : allNodesAndHosts.childrenOf(candidate.parentHostname().get())) { + for (Node nodeOnHost : allNodes.childrenOf(candidate.parentHostname().get())) { if (nodeOnHost.allocation().isEmpty()) continue; if (requestedNodes.isExclusive() || nodeOnHost.allocation().get().membership().cluster().isExclusive()) { if ( ! nodeOnHost.allocation().get().owner().equals(application)) return true; @@ -289,7 +288,7 @@ class NodeAllocation { } private Node resize(Node node) { - NodeResources hostResources = allNodesAndHosts.parentOf(node).get().flavor().resources(); + NodeResources hostResources = allNodes.parentOf(node).get().flavor().resources(); return node.with(new Flavor(requestedNodes.resources().get() .with(hostResources.diskSpeed()) .with(hostResources.storageType()) @@ -341,7 +340,7 @@ class NodeAllocation { if (hostType == NodeType.host) return nodeRepository.database().readProvisionIndices(count); // Infrastructure hosts have fixed indices, starting at 1 - Set<Integer> currentIndices = allNodesAndHosts.nodes().nodeType(hostType) + Set<Integer> currentIndices = allNodes.nodeType(hostType) .hostnames() .stream() // TODO(mpolden): Use cluster index instead of parsing hostname, once all @@ -441,7 +440,7 @@ class NodeAllocation { if (nodeType() == NodeType.tenant) return accepted; // Infrastructure nodes are always allocated by type. Count all nodes as accepted so that we never exceed // the wanted number of nodes for the type. - return allNodesAndHosts.nodes().nodeType(nodeType()).size(); + return allNodes.nodeType(nodeType()).size(); } /** Prefer to retire nodes we want the least */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java index 79d05ce5c97..5e95d36fcfb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java @@ -7,7 +7,6 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Nodes; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; @@ -31,7 +30,7 @@ import java.util.stream.Collectors; public class NodePrioritizer { private final List<NodeCandidate> candidates = new ArrayList<>(); - private final NodesAndHosts<LockedNodeList> allNodesAndHosts; + private final LockedNodeList allNodes; private final HostCapacity capacity; private final NodeSpec requestedNodes; private final ApplicationId application; @@ -47,23 +46,23 @@ public class NodePrioritizer { private final Set<Node> spareHosts; private final boolean enclave; - public NodePrioritizer(NodesAndHosts<LockedNodeList> allNodesAndHosts, ApplicationId application, ClusterSpec clusterSpec, NodeSpec nodeSpec, + public NodePrioritizer(LockedNodeList allNodes, ApplicationId application, ClusterSpec clusterSpec, NodeSpec nodeSpec, int wantedGroups, boolean dynamicProvisioning, NameResolver nameResolver, Nodes nodes, HostResourcesCalculator hostResourcesCalculator, int spareCount, boolean enclave) { - this.allNodesAndHosts = allNodesAndHosts; - this.capacity = new HostCapacity(this.allNodesAndHosts, hostResourcesCalculator); + this.allNodes = allNodes; + this.capacity = new HostCapacity(this.allNodes, hostResourcesCalculator); this.requestedNodes = nodeSpec; this.clusterSpec = clusterSpec; this.application = application; this.dynamicProvisioning = dynamicProvisioning; this.spareHosts = dynamicProvisioning ? - capacity.findSpareHostsInDynamicallyProvisionedZones(this.allNodesAndHosts.nodes().asList()) : - capacity.findSpareHosts(this.allNodesAndHosts.nodes().asList(), spareCount); + capacity.findSpareHostsInDynamicallyProvisionedZones(this.allNodes.asList()) : + capacity.findSpareHosts(this.allNodes.asList(), spareCount); this.nameResolver = nameResolver; this.nodes = nodes; this.enclave = enclave; - NodeList nodesInCluster = this.allNodesAndHosts.nodes().owner(application).type(clusterSpec.type()).cluster(clusterSpec.id()); + NodeList nodesInCluster = this.allNodes.owner(application).type(clusterSpec.type()).cluster(clusterSpec.id()); NodeList nonRetiredNodesInCluster = nodesInCluster.not().retired(); long currentGroups = nonRetiredNodesInCluster.state(Node.State.active).stream() .flatMap(node -> node.allocation() @@ -139,7 +138,7 @@ public class NodePrioritizer { private void addCandidatesOnExistingHosts() { if ( !canAllocateNew) return; - for (Node host : allNodesAndHosts.nodes()) { + for (Node host : allNodes) { if ( ! nodes.canAllocateTenantNodeTo(host, dynamicProvisioning)) continue; if (host.reservedTo().isPresent() && !host.reservedTo().get().equals(application.tenant())) continue; if (host.reservedTo().isPresent() && application.instance().isTester()) continue; @@ -147,13 +146,13 @@ public class NodePrioritizer { if ( ! host.exclusiveToClusterType().map(clusterSpec.type()::equals).orElse(true)) continue; if (spareHosts.contains(host) && !canAllocateToSpareHosts) continue; if ( ! capacity.hasCapacity(host, requestedNodes.resources().get())) continue; - if ( ! allNodesAndHosts.childrenOf(host).owner(application).cluster(clusterSpec.id()).isEmpty()) continue; + if ( ! allNodes.childrenOf(host).owner(application).cluster(clusterSpec.id()).isEmpty()) continue; candidates.add(NodeCandidate.createNewChild(requestedNodes.resources().get(), capacity.availableCapacityOf(host), host, spareHosts.contains(host), - allNodesAndHosts.nodes(), + allNodes, nameResolver, !enclave)); } @@ -162,7 +161,7 @@ public class NodePrioritizer { /** Add existing nodes allocated to the application */ private void addApplicationNodes() { EnumSet<Node.State> legalStates = EnumSet.of(Node.State.active, Node.State.inactive, Node.State.reserved); - allNodesAndHosts.nodes().stream() + allNodes.stream() .filter(node -> node.type() == requestedNodes.type()) .filter(node -> legalStates.contains(node.state())) .filter(node -> node.allocation().isPresent()) @@ -175,7 +174,7 @@ public class NodePrioritizer { /** Add nodes already provisioned, but not allocated to any application */ private void addReadyNodes() { - allNodesAndHosts.nodes().stream() + allNodes.stream() .filter(node -> node.type() == requestedNodes.type()) .filter(node -> node.state() == Node.State.ready) .map(node -> candidateFrom(node, false)) @@ -185,7 +184,7 @@ public class NodePrioritizer { /** Create a candidate from given pre-existing node */ private NodeCandidate candidateFrom(Node node, boolean isSurplus) { - Optional<Node> optionalParent = allNodesAndHosts.parentOf(node); + Optional<Node> optionalParent = allNodes.parentOf(node); if (optionalParent.isPresent()) { Node parent = optionalParent.get(); return NodeCandidate.createChild(node, @@ -223,7 +222,7 @@ public class NodePrioritizer { */ private boolean canStillAllocate(Node node) { if (node.type() != NodeType.tenant || node.parentHostname().isEmpty()) return true; - Optional<Node> parent = allNodesAndHosts.parentOf(node); + Optional<Node> parent = allNodes.parentOf(node); return parent.isPresent() && nodes.canAllocateTenantNodeTo(parent.get(), dynamicProvisioning); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java index 139e8848ab1..b6c7324c75c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java @@ -9,13 +9,11 @@ import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Optional; -import java.util.stream.Collectors; /** * Performs preparation of node activation changes for an application. @@ -58,8 +56,8 @@ class Preparer { // active config model which is changed on activate private List<Node> prepareNodes(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) { - NodesAndHosts<LockedNodeList> allNodesAndHosts = groupPreparer.createNodesAndHostUnlocked(); - NodeList appNodes = allNodesAndHosts.nodes().owner(application); + LockedNodeList allNodes = groupPreparer.createUnlockedNodeList(); + NodeList appNodes = allNodes.owner(application); List<Node> surplusNodes = findNodesInRemovableGroups(appNodes, cluster, wantedGroups); List<Integer> usedIndices = appNodes.cluster(cluster.id()).mapToList(node -> node.allocation().get().membership().index()); @@ -71,11 +69,11 @@ class Preparer { GroupPreparer.PrepareResult result = groupPreparer.prepare(application, clusterGroup, requestedNodes.fraction(wantedGroups), surplusNodes, indices, wantedGroups, - allNodesAndHosts); - allNodesAndHosts = result.allNodesAndHosts; // Might have changed - List<Node> accepted = result.prepared; + allNodes); + allNodes = result.allNodes(); // Might have changed + List<Node> accepted = result.prepared(); if (requestedNodes.rejectNonActiveParent()) { - NodeList activeHosts = allNodesAndHosts.nodes().state(Node.State.active).parents().nodeType(requestedNodes.type().hostType()); + NodeList activeHosts = allNodes.state(Node.State.active).parents().nodeType(requestedNodes.type().hostType()); accepted = accepted.stream() .filter(node -> node.parentHostname().isEmpty() || activeHosts.parentOf(node).isPresent()) .toList(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java index 2327b40885f..23c2d0fc47a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java @@ -547,7 +547,7 @@ public class DynamicAllocationTest { } private List<Node> findSpareCapacity(ProvisioningTester tester) { - NodeList nodes = tester.nodeRepository().nodes().list(State.values()); + NodeList nodes = tester.nodeRepository().nodes().list(); return nodes.nodeType(NodeType.host) .matching(host -> nodes.childrenOf(host).size() == 0) // Hosts without children .asList(); diff --git a/parent/pom.xml b/parent/pom.xml index d1fc3e4f76a..a9c3cfa82f8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -900,9 +900,9 @@ <version>${eclipse-collections.version}</version> </dependency> <dependency> - <groupId>org.eclipse.jetty.alpn</groupId> - <artifactId>alpn-api</artifactId> - <version>${jetty-alpn.version}</version> + <groupId>org.eclipse.jetty.http2</groupId> + <artifactId>http2-common</artifactId> + <version>${jetty.version}</version> </dependency> <dependency> <groupId>org.eclipse.jetty.http2</groupId> @@ -916,12 +916,12 @@ </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-client</artifactId> + <artifactId>jetty-alpn-server</artifactId> <version>${jetty.version}</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-continuation</artifactId> + <artifactId>jetty-client</artifactId> <version>${jetty.version}</version> </dependency> <dependency> @@ -931,22 +931,22 @@ </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-server</artifactId> + <artifactId>jetty-io</artifactId> <version>${jetty.version}</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlet</artifactId> + <artifactId>jetty-jmx</artifactId> <version>${jetty.version}</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-servlets</artifactId> + <artifactId>jetty-server</artifactId> <version>${jetty.version}</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> - <artifactId>jetty-jmx</artifactId> + <artifactId>jetty-servlet</artifactId> <version>${jetty.version}</version> </dependency> <dependency> @@ -955,6 +955,11 @@ <version>${jetty.version}</version> </dependency> <dependency> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>jetty-jakarta-servlet-api</artifactId> + <version>${jetty-servlet-api.version}</version> + </dependency> + <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.2</version> <!-- 2.3.3 has a BROKEN manifest --> @@ -1117,8 +1122,8 @@ <felix.log.version>1.0.1</felix.log.version> <findbugs.version>3.0.2</findbugs.version> <!-- Should be kept in sync with guava --> <hdrhistogram.version>2.1.12</hdrhistogram.version> - <jetty.version>9.4.49.v20220914</jetty.version> - <jetty-alpn.version>1.1.3.v20160715</jetty-alpn.version> + <jetty.version>11.0.13</jetty.version> + <jetty-servlet-api.version>5.0.2</jetty-servlet-api.version> <jjwt.version>0.11.2</jjwt.version> <jna.version>5.11.0</jna.version> <junit.version>5.8.1</junit.version> diff --git a/screwdriver.yaml b/screwdriver.yaml index 182abb95968..d918078b80e 100644 --- a/screwdriver.yaml +++ b/screwdriver.yaml @@ -128,6 +128,7 @@ jobs: # Set correct version in pom.xml files (cd vespa && screwdriver/replace-vespa-version-in-poms.sh $VESPA_VERSION $(pwd) ) (cd sample-apps && find . -name "pom.xml" -exec sed -i -e "s,<vespa_version>.*</vespa_version>,<vespa_version>$VESPA_VERSION</vespa_version>," {} \;) + (cd sample-apps && find . -name "pom.xml" -exec sed -i -e "s:<version>[[]8,9[)]</version>:<version>$VESPA_VERSION</version>:" {} \;) - make-srpm: | make -C $WORKDIR/vespa -f .copr/Makefile srpm outdir=$WORKDIR - *restore-cache diff --git a/searchcore/src/tests/proton/matching/index_environment/index_environment_test.cpp b/searchcore/src/tests/proton/matching/index_environment/index_environment_test.cpp index 9acc6eed669..60c60f0a37e 100644 --- a/searchcore/src/tests/proton/matching/index_environment/index_environment_test.cpp +++ b/searchcore/src/tests/proton/matching/index_environment/index_environment_test.cpp @@ -17,6 +17,7 @@ using search::index::schema::DataType; using vespalib::eval::ConstantValue; using SAF = Schema::AttributeField; using SIAF = Schema::ImportedAttributeField; +using SIF = Schema::IndexField; const vespalib::string my_expr_ref( "this is my reference ranking expression.\n" @@ -157,21 +158,26 @@ Schema::UP schema_with_virtual_fields() { result->addAttributeField(SAF("person_map.value.year", DataType::INT32, CollectionType::ARRAY)); result->addImportedAttributeField(SAF("int_map.key", DataType::INT32, CollectionType::ARRAY)); result->addImportedAttributeField(SAF("int_map.value", DataType::INT32, CollectionType::ARRAY)); + // Index fields do not represent virtual fields: + result->addIndexField(SIF("url.hostname", DataType::STRING, CollectionType::SINGLE)); + result->addIndexField(SIF("url.port", DataType::STRING, CollectionType::SINGLE)); return result; } TEST_F("virtual fields are extracted in index environment", Fixture(schema_with_virtual_fields())) { - ASSERT_EQUAL(9u, f.env.getNumFields()); + ASSERT_EQUAL(11u, f.env.getNumFields()); TEST_DO(f.assertAttributeField(0, "person_map.key", DataType::INT32, CollectionType::ARRAY)); TEST_DO(f.assertAttributeField(1, "person_map.value.name", DataType::STRING, CollectionType::ARRAY)); TEST_DO(f.assertAttributeField(2, "person_map.value.year", DataType::INT32, CollectionType::ARRAY)); - TEST_DO(f.assertAttributeField(3, "int_map.key", DataType::INT32, CollectionType::ARRAY)); - TEST_DO(f.assertAttributeField(4, "int_map.value", DataType::INT32, CollectionType::ARRAY)); - EXPECT_EQUAL("[documentmetastore]", f.env.getField(5)->name()); - TEST_DO(f.assert_virtual_field(6, "int_map")); - TEST_DO(f.assert_virtual_field(7, "person_map")); - TEST_DO(f.assert_virtual_field(8, "person_map.value")); + TEST_DO(f.assertField(3, "url.hostname", DataType::STRING, CollectionType::SINGLE)); + TEST_DO(f.assertField(4, "url.port", DataType::STRING, CollectionType::SINGLE)); + TEST_DO(f.assertAttributeField(5, "int_map.key", DataType::INT32, CollectionType::ARRAY)); + TEST_DO(f.assertAttributeField(6, "int_map.value", DataType::INT32, CollectionType::ARRAY)); + EXPECT_EQUAL("[documentmetastore]", f.env.getField(7)->name()); + TEST_DO(f.assert_virtual_field(8, "int_map")); + TEST_DO(f.assert_virtual_field(9, "person_map")); + TEST_DO(f.assert_virtual_field(10, "person_map.value")); } TEST_F("require that onnx model config can be obtained", Fixture(buildEmptySchema())) { diff --git a/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp b/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp index 6638b238e03..6fceb0db87f 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp @@ -32,7 +32,9 @@ extract_virtual_fields(const std::vector<search::fef::FieldInfo>& fields) // These attributes have '.' in their names, example: my_map.key and my_map.value represent a map<int, string>. StringSet result; for (const auto& field : fields) { - consider_field_for_extraction(field.name(), result); + if (field.hasAttribute()) { + consider_field_for_extraction(field.name(), result); + } } return result; } diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp index d00342d0e51..2f51459ebfa 100644 --- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp +++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp @@ -887,6 +887,7 @@ public: } void test_setup(); void test_save_load(bool multi_node); + void test_address_space_usage(); }; template <HnswIndexType type> @@ -935,6 +936,23 @@ TensorAttributeHnswIndex<type>::test_save_load(bool multi_node) expect_level_0(1, index_b.get_node(2)); } +template <HnswIndexType type> +void +TensorAttributeHnswIndex<type>::test_address_space_usage() +{ + bool dense = type == HnswIndexType::SINGLE; + search::AddressSpaceUsage usage = _attr->getAddressSpaceUsage(); + const auto& all = usage.get_all(); + EXPECT_EQUAL(dense ? 3u : 5u, all.size()); + EXPECT_EQUAL(1u, all.count("tensor-store")); + EXPECT_EQUAL(1u, all.count("hnsw-levels-store")); + EXPECT_EQUAL(1u, all.count("hnsw-links-store")); + if (!dense) { + EXPECT_EQUAL(1u, all.count("hnsw-nodeid-mapping")); + EXPECT_EQUAL(1u, all.count("shared-string-repo")); + } +} + class DenseTensorAttributeHnswIndex : public TensorAttributeHnswIndex<HnswIndexType::SINGLE> { public: DenseTensorAttributeHnswIndex() : TensorAttributeHnswIndex<HnswIndexType::SINGLE>(vec_2d_spec, FixtureTraits().hnsw()) {} @@ -970,16 +988,15 @@ TEST_F("Hnsw index is integrated in mixed tensor attribute and can be saved and f.test_save_load(true); } -TEST_F("Populates address space usage", DenseTensorAttributeHnswIndex) +TEST_F("Populates address space usage in dense tensor attribute with hnsw index", DenseTensorAttributeHnswIndex) { - search::AddressSpaceUsage usage = f._attr->getAddressSpaceUsage(); - const auto& all = usage.get_all(); - EXPECT_EQUAL(3u, all.size()); - EXPECT_EQUAL(1u, all.count("tensor-store")); - EXPECT_EQUAL(1u, all.count("hnsw-levels-store")); - EXPECT_EQUAL(1u, all.count("hnsw-links-store")); + f.test_address_space_usage(); } +TEST_F("Populates address space usage in mixed tensor attribute with hnsw index", MixedTensorAttributeHnswIndex) +{ + f.test_address_space_usage(); +} class DenseTensorAttributeMockIndex : public Fixture { public: diff --git a/searchlib/src/vespa/searchlib/attribute/address_space_components.cpp b/searchlib/src/vespa/searchlib/attribute/address_space_components.cpp index 244e01a3874..75c198f71e2 100644 --- a/searchlib/src/vespa/searchlib/attribute/address_space_components.cpp +++ b/searchlib/src/vespa/searchlib/attribute/address_space_components.cpp @@ -21,5 +21,6 @@ const vespalib::string AddressSpaceComponents::tensor_store = "tensor-store"; const vespalib::string AddressSpaceComponents::shared_string_repo = "shared-string-repo"; const vespalib::string AddressSpaceComponents::hnsw_levels_store = "hnsw-levels-store"; const vespalib::string AddressSpaceComponents::hnsw_links_store = "hnsw-links-store"; +const vespalib::string AddressSpaceComponents::hnsw_nodeid_mapping = "hnsw-nodeid-mapping"; } diff --git a/searchlib/src/vespa/searchlib/attribute/address_space_components.h b/searchlib/src/vespa/searchlib/attribute/address_space_components.h index a8a41a024f8..20302a51f0d 100644 --- a/searchlib/src/vespa/searchlib/attribute/address_space_components.h +++ b/searchlib/src/vespa/searchlib/attribute/address_space_components.h @@ -20,6 +20,7 @@ public: static const vespalib::string shared_string_repo; static const vespalib::string hnsw_levels_store; static const vespalib::string hnsw_links_store; + static const vespalib::string hnsw_nodeid_mapping; }; } diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp index 5483a0d2de4..1529611753b 100644 --- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp +++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp @@ -706,6 +706,9 @@ HnswIndex<type>::populate_address_space_usage(search::AddressSpaceUsage& usage) { usage.set(AddressSpaceComponents::hnsw_levels_store, _graph.levels_store.addressSpaceUsage()); usage.set(AddressSpaceComponents::hnsw_links_store, _graph.links_store.addressSpaceUsage()); + if constexpr (type == HnswIndexType::MULTI) { + usage.set(AddressSpaceComponents::hnsw_nodeid_mapping, _id_mapping.address_space_usage()); + } } template <HnswIndexType type> diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_nodeid_mapping.h b/searchlib/src/vespa/searchlib/tensor/hnsw_nodeid_mapping.h index 8adf621b9b3..b544eb6f09f 100644 --- a/searchlib/src/vespa/searchlib/tensor/hnsw_nodeid_mapping.h +++ b/searchlib/src/vespa/searchlib/tensor/hnsw_nodeid_mapping.h @@ -55,7 +55,7 @@ public: void assign_generation(generation_t current_gen); void reclaim_memory(generation_t oldest_used_gen); void on_load(vespalib::ConstArrayRef<HnswNode> nodes); - // TODO: Add support for compaction + vespalib::AddressSpace address_space_usage() const { return _nodeids.addressSpaceUsage(); } vespalib::MemoryUsage memory_usage() const; vespalib::MemoryUsage update_stat(const vespalib::datastore::CompactionStrategy& compaction_strategy); bool consider_compact() const noexcept { return _nodeids.consider_compact(); } diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt index 1e38b6029ba..5a29ad22c51 100644 --- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt +++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt @@ -2,8 +2,8 @@ #[non-test] # Contains dependencies that are not used exclusively in 'test' scope -ai.djl.huggingface:tokenizers:0.20.0 ai.djl:api:0.20.0 +ai.djl.huggingface:tokenizers:0.20.0 aopalliance:aopalliance:1.0 backport-util-concurrent:backport-util-concurrent:3.1 biz.aQute.bnd:biz.aQute.bnd.util:6.1.0 @@ -146,23 +146,21 @@ org.codehaus.plexus:plexus-sec-dispatcher:2.0 org.codehaus.plexus:plexus-utils:3.3.1 org.eclipse.collections:eclipse-collections:11.0.0 org.eclipse.collections:eclipse-collections-api:11.0.0 -org.eclipse.jetty:jetty-alpn-java-server:9.4.49.v20220914 -org.eclipse.jetty:jetty-alpn-server:9.4.49.v20220914 -org.eclipse.jetty:jetty-client:9.4.49.v20220914 -org.eclipse.jetty:jetty-continuation:9.4.49.v20220914 -org.eclipse.jetty:jetty-http:9.4.49.v20220914 -org.eclipse.jetty:jetty-io:9.4.49.v20220914 -org.eclipse.jetty:jetty-jmx:9.4.49.v20220914 -org.eclipse.jetty:jetty-security:9.4.49.v20220914 -org.eclipse.jetty:jetty-server:9.4.49.v20220914 -org.eclipse.jetty:jetty-servlet:9.4.49.v20220914 -org.eclipse.jetty:jetty-servlets:9.4.49.v20220914 -org.eclipse.jetty:jetty-util:9.4.49.v20220914 -org.eclipse.jetty:jetty-util-ajax:9.4.49.v20220914 -org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 -org.eclipse.jetty.http2:http2-common:9.4.49.v20220914 -org.eclipse.jetty.http2:http2-hpack:9.4.49.v20220914 -org.eclipse.jetty.http2:http2-server:9.4.49.v20220914 +org.eclipse.jetty:jetty-alpn-client:11.0.13 +org.eclipse.jetty:jetty-alpn-java-server:11.0.13 +org.eclipse.jetty:jetty-alpn-server:11.0.13 +org.eclipse.jetty:jetty-client:11.0.13 +org.eclipse.jetty:jetty-http:11.0.13 +org.eclipse.jetty:jetty-io:11.0.13 +org.eclipse.jetty:jetty-jmx:11.0.13 +org.eclipse.jetty:jetty-security:11.0.13 +org.eclipse.jetty:jetty-server:11.0.13 +org.eclipse.jetty:jetty-servlet:11.0.13 +org.eclipse.jetty:jetty-util:11.0.13 +org.eclipse.jetty.http2:http2-common:11.0.13 +org.eclipse.jetty.http2:http2-hpack:11.0.13 +org.eclipse.jetty.http2:http2-server:11.0.13 +org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2 org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5 org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5 org.fusesource.jansi:jansi:1.18 diff --git a/vespalib/src/tests/coro/async_io/async_io_test.cpp b/vespalib/src/tests/coro/async_io/async_io_test.cpp index a506a5dd0d4..f5098e30086 100644 --- a/vespalib/src/tests/coro/async_io/async_io_test.cpp +++ b/vespalib/src/tests/coro/async_io/async_io_test.cpp @@ -4,14 +4,22 @@ #include <vespa/vespalib/coro/detached.h> #include <vespa/vespalib/coro/completion.h> #include <vespa/vespalib/coro/async_io.h> +#include <vespa/vespalib/coro/async_crypto_socket.h> #include <vespa/vespalib/net/socket_spec.h> #include <vespa/vespalib/net/server_socket.h> #include <vespa/vespalib/net/socket_handle.h> #include <vespa/vespalib/net/socket_address.h> +#include <vespa/vespalib/net/crypto_engine.h> +#include <vespa/vespalib/util/require.h> +#include <vespa/vespalib/util/classname.h> +#include <vespa/vespalib/test/make_tls_options_for_testing.h> +#include <vespa/vespalib/net/tls/tls_crypto_engine.h> +#include <vespa/vespalib/net/tls/maybe_tls_crypto_engine.h> #include <vespa/vespalib/gtest/gtest.h> using namespace vespalib; using namespace vespalib::coro; +using namespace vespalib::test; Detached self_exiting_run_loop(AsyncIo::SP async) { for (size_t i = 0; co_await async->schedule(); ++i) { @@ -53,11 +61,11 @@ TEST(AsyncIoTest, shutdown_with_self_exiting_coroutine) { f2.wait(); } -Lazy<size_t> write_msg(AsyncIo &async, SocketHandle &socket, const vespalib::string &msg) { +Lazy<size_t> write_msg(AsyncCryptoSocket &socket, const vespalib::string &msg) { size_t written = 0; while (written < msg.size()) { size_t write_size = (msg.size() - written); - ssize_t write_result = co_await async.write(socket, msg.data() + written, write_size); + ssize_t write_result = co_await socket.write(msg.data() + written, write_size); if (write_result <= 0) { co_return written; } @@ -66,12 +74,12 @@ Lazy<size_t> write_msg(AsyncIo &async, SocketHandle &socket, const vespalib::str co_return written; } -Lazy<vespalib::string> read_msg(AsyncIo &async, SocketHandle &socket, size_t wanted_bytes) { +Lazy<vespalib::string> read_msg(AsyncCryptoSocket &socket, size_t wanted_bytes) { char tmp[64]; vespalib::string result; while (result.size() < wanted_bytes) { size_t read_size = std::min(sizeof(tmp), wanted_bytes - result.size()); - ssize_t read_result = co_await async.read(socket, tmp, read_size); + ssize_t read_result = co_await socket.read(tmp, read_size); if (read_result <= 0) { co_return result; } @@ -80,50 +88,78 @@ Lazy<vespalib::string> read_msg(AsyncIo &async, SocketHandle &socket, size_t wan co_return result; } -Work verify_socket_io(AsyncIo &async, SocketHandle &socket, bool is_server) { +Work verify_socket_io(AsyncCryptoSocket &socket, bool is_server) { vespalib::string server_message = "hello, this is the server speaking"; vespalib::string client_message = "please pick up, I need to talk to you"; if (is_server) { - vespalib::string read = co_await read_msg(async, socket, client_message.size()); + vespalib::string read = co_await read_msg(socket, client_message.size()); EXPECT_EQ(client_message, read); - size_t written = co_await write_msg(async, socket, server_message); + size_t written = co_await write_msg(socket, server_message); EXPECT_EQ(written, ssize_t(server_message.size())); } else { - size_t written = co_await write_msg(async, socket, client_message); + size_t written = co_await write_msg(socket, client_message); EXPECT_EQ(written, ssize_t(client_message.size())); - vespalib::string read = co_await read_msg(async, socket, server_message.size()); + vespalib::string read = co_await read_msg(socket, server_message.size()); EXPECT_EQ(server_message, read); } co_return Done{}; } -Work async_server(AsyncIo &async, ServerSocket &server_socket) { +Work async_server(AsyncIo &async, CryptoEngine &engine, ServerSocket &server_socket) { auto server_addr = server_socket.address(); auto server_spec = server_addr.spec(); fprintf(stderr, "listening at '%s' (fd = %d)\n", server_spec.c_str(), server_socket.get_fd()); - auto socket = co_await async.accept(server_socket); - fprintf(stderr, "server fd: %d\n", socket.get()); - co_return co_await verify_socket_io(async, socket, true); + auto raw_socket = co_await async.accept(server_socket); + fprintf(stderr, "server fd: %d\n", raw_socket.get()); + auto socket = co_await AsyncCryptoSocket::accept(async, engine, std::move(raw_socket)); + EXPECT_TRUE(socket); + REQUIRE(socket); + fprintf(stderr, "server socket type: %s\n", getClassName(*socket).c_str()); + co_return co_await verify_socket_io(*socket, true); } -Work async_client(AsyncIo &async, ServerSocket &server_socket) { +Work async_client(AsyncIo &async, CryptoEngine &engine, ServerSocket &server_socket) { auto server_addr = server_socket.address(); - auto server_spec = server_addr.spec(); - fprintf(stderr, "connecting to '%s'\n", server_spec.c_str()); - auto client_addr = SocketSpec(server_spec).client_address(); - auto socket = co_await async.connect(client_addr); - fprintf(stderr, "client fd: %d\n", socket.get()); - co_return co_await verify_socket_io(async, socket, false); + auto server_spec = SocketSpec(server_addr.spec()); + fprintf(stderr, "connecting to '%s'\n", server_spec.spec().c_str()); + auto client_addr = server_spec.client_address(); + auto raw_socket = co_await async.connect(client_addr); + fprintf(stderr, "client fd: %d\n", raw_socket.get()); + auto socket = co_await AsyncCryptoSocket::connect(async, engine, std::move(raw_socket), server_spec); + EXPECT_TRUE(socket); + REQUIRE(socket); + fprintf(stderr, "client socket type: %s\n", getClassName(*socket).c_str()); + co_return co_await verify_socket_io(*socket, false); } -TEST(AsyncIoTest, raw_socket_io) { +void verify_socket_io(CryptoEngine &engine) { ServerSocket server_socket("tcp/0"); server_socket.set_blocking(false); auto async = AsyncIo::create(); - auto f1 = make_future(async_server(async, server_socket)); - auto f2 = make_future(async_client(async, server_socket)); - f1.wait(); - f2.wait(); + auto f1 = make_future(async_server(async, engine, server_socket)); + auto f2 = make_future(async_client(async, engine, server_socket)); + (void) f1.get(); + (void) f2.get(); +} + +TEST(AsyncIoTest, raw_socket_io) { + NullCryptoEngine engine; + verify_socket_io(engine); +} + +TEST(AsyncIoTest, tls_socket_io) { + TlsCryptoEngine engine(make_tls_options_for_testing()); + verify_socket_io(engine); +} + +TEST(AsyncIoTest, maybe_tls_true_socket_io) { + MaybeTlsCryptoEngine engine(std::make_shared<TlsCryptoEngine>(make_tls_options_for_testing()), true); + verify_socket_io(engine); +} + +TEST(AsyncIoTest, maybe_tls_false_socket_io) { + MaybeTlsCryptoEngine engine(std::make_shared<TlsCryptoEngine>(make_tls_options_for_testing()), false); + verify_socket_io(engine); } GTEST_MAIN_RUN_ALL_TESTS() diff --git a/vespalib/src/vespa/vespalib/btree/btreeaggregator.h b/vespalib/src/vespa/vespalib/btree/btreeaggregator.h index 8a15d1ca34f..13109f5ffae 100644 --- a/vespalib/src/vespa/vespalib/btree/btreeaggregator.h +++ b/vespalib/src/vespa/vespalib/btree/btreeaggregator.h @@ -34,7 +34,7 @@ public: static void recalc(InternalNodeType &node, const NodeAllocatorType &allocator, const AggrCalcT &aggrCalc); static AggregatedType recalc(LeafNodeType &node, LeafNodeType &splitNode, const AggrCalcT &aggrCalc); - + static AggregatedType recalc(InternalNodeType &node, InternalNodeType &splitNode, const NodeAllocatorType &allocator, const AggrCalcT &aggrCalc); }; diff --git a/vespalib/src/vespa/vespalib/btree/btreeinserter.h b/vespalib/src/vespa/vespalib/btree/btreeinserter.h index 2a4985b04b8..947213ab7e6 100644 --- a/vespalib/src/vespa/vespalib/btree/btreeinserter.h +++ b/vespalib/src/vespa/vespalib/btree/btreeinserter.h @@ -8,7 +8,7 @@ #include "btreeaggregator.h" #include "noaggrcalc.h" #include "minmaxaggrcalc.h" -#include "btreeiterator.h" +#include "btreeiterator.h" namespace vespalib::btree { diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.h b/vespalib/src/vespa/vespalib/btree/btreeiterator.h index 15061a536ef..4b99edf592a 100644 --- a/vespalib/src/vespa/vespalib/btree/btreeiterator.h +++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.h @@ -297,7 +297,7 @@ protected: BTreeIteratorBase(const BTreeIteratorBase &other); BTreeIteratorBase &operator=(const BTreeIteratorBase &other); - + /** * Set new tree height and clear portions of path that are now * beyond new tree height. For internal use only. @@ -414,7 +414,7 @@ public: size_t size() const; - + /** * Return the current position in the tree. */ @@ -485,7 +485,7 @@ public: rbegin(); /* - * Get aggregated values for the current tree. + * Get aggregated values for the current tree. */ const AggrT & getAggregated() const; @@ -697,7 +697,7 @@ public: * Step iterator forwards until it is at a position with a key * that is greater than or equal to the key argument. Original * position must be valid with a key that is less than the key argument. - * + * * Tree traits determine if binary or linear search is performed within * each tree node. * @@ -711,7 +711,7 @@ public: * Step iterator forwards until it is at a position with a key * that is greater than or equal to the key argument. Original * position must be valid with a key that is less than the key argument. - * + * * Binary search is performed within each tree node. * * @param key Key to search for @@ -724,7 +724,7 @@ public: * Step iterator forwards until it is at a position with a key * that is greater than or equal to the key argument. Original * position must be valid with a key that is less than the key argument. - * + * * Linear search is performed within each tree node. * * @param key Key to search for @@ -737,7 +737,7 @@ public: * Step iterator forwards until it is at a position with a key * that is greater than the key argument. Original position must * be valid with a key that is less than or equal to the key argument. - * + * * Tree traits determine if binary or linear search is performed within * each tree node. * @@ -751,7 +751,7 @@ public: * Step iterator forwards until it is at a position with a key * that is greater than the key argument. Original position must * be valid with a key that is less than or equal to the key argument. - * + * * Binary search is performed within each tree node. * * @param key Key to search for @@ -764,7 +764,7 @@ public: * Step iterator forwards until it is at a position with a key * that is greater than the key argument. Original position must * be valid with a key that is less than or equal to the key argument. - * + * * Linear search is performed within each tree node. * * @param key Key to search for @@ -868,7 +868,7 @@ public: { return const_cast<NodeAllocatorType &>(*_allocator); } - + BTreeNode::Ref moveFirstLeafNode(BTreeNode::Ref rootRef); diff --git a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h index 38739f03798..3fa5f1188cd 100644 --- a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h +++ b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h @@ -42,7 +42,7 @@ private: using RefVector = vespalib::Array<BTreeNode::Ref>; using BTreeRootBaseTypeVector = vespalib::Array<BTreeRootBaseType *>; - + // Nodes that might not be frozen. RefVector _internalToFreeze; RefVector _leafToFreeze; diff --git a/vespalib/src/vespa/vespalib/btree/btreenodestore.h b/vespalib/src/vespa/vespalib/btree/btreenodestore.h index 3b37be33924..8fef0185674 100644 --- a/vespalib/src/vespa/vespalib/btree/btreenodestore.h +++ b/vespalib/src/vespa/vespalib/btree/btreenodestore.h @@ -189,7 +189,7 @@ public: bool has_held_buffers() const { return _store.has_held_buffers(); } - + template <typename FunctionType> void foreach_key(EntryRef ref, FunctionType func) const { if (!ref.valid()) diff --git a/vespalib/src/vespa/vespalib/btree/btreeremover.h b/vespalib/src/vespa/vespalib/btree/btreeremover.h index 33ecc0be88b..852a8aa2104 100644 --- a/vespalib/src/vespa/vespalib/btree/btreeremover.h +++ b/vespalib/src/vespa/vespalib/btree/btreeremover.h @@ -8,7 +8,7 @@ #include "btreeaggregator.h" #include "noaggrcalc.h" #include "minmaxaggrcalc.h" -#include "btreeiterator.h" +#include "btreeiterator.h" namespace vespalib::btree { @@ -56,7 +56,7 @@ class BTreeRemover : public BTreeRemoverBase<KeyT, DataT, AggrT, TraitsT::INTERNAL_SLOTS, TraitsT::LEAF_SLOTS, AggrCalcT> - + { public: using ParentType = BTreeRemoverBase<KeyT, DataT, AggrT, diff --git a/vespalib/src/vespa/vespalib/btree/btreerootbase.h b/vespalib/src/vespa/vespalib/btree/btreerootbase.h index 6b57dda7660..8589f43d909 100644 --- a/vespalib/src/vespa/vespalib/btree/btreerootbase.h +++ b/vespalib/src/vespa/vespalib/btree/btreerootbase.h @@ -54,7 +54,7 @@ public: // entry for _root is owned by new copy of BTreeRootBase. _root = BTreeNode::Ref(); } - + void setRoots(BTreeNode::Ref newRoot) { _root = newRoot; _frozenRoot = newRoot.ref(); diff --git a/vespalib/src/vespa/vespalib/btree/btreestore.h b/vespalib/src/vespa/vespalib/btree/btreestore.h index 5250cfa4535..c228c084e6d 100644 --- a/vespalib/src/vespa/vespalib/btree/btreestore.h +++ b/vespalib/src/vespa/vespalib/btree/btreestore.h @@ -55,7 +55,7 @@ public: using BufferState = datastore::BufferState; static constexpr uint32_t clusterLimit = 8; - + enum BufferTypes { BUFFERTYPE_ARRAY1 = 0, @@ -209,13 +209,13 @@ public: applyNewArray(EntryRef &ref, AddIter aOrg, AddIter ae); - + void applyNewTree(EntryRef &ref, AddIter a, AddIter ae, CompareT comp); - + void applyNew(EntryRef &ref, AddIter a, diff --git a/vespalib/src/vespa/vespalib/btree/minmaxaggregated.h b/vespalib/src/vespa/vespalib/btree/minmaxaggregated.h index 4b7a8a85db3..802f9822d71 100644 --- a/vespalib/src/vespa/vespalib/btree/minmaxaggregated.h +++ b/vespalib/src/vespa/vespalib/btree/minmaxaggregated.h @@ -11,7 +11,7 @@ class MinMaxAggregated { int32_t _min; int32_t _max; - + public: MinMaxAggregated() : _min(std::numeric_limits<int32_t>::max()), diff --git a/vespalib/src/vespa/vespalib/coro/CMakeLists.txt b/vespalib/src/vespa/vespalib/coro/CMakeLists.txt index 0fbb94e8255..8a7a0ade049 100644 --- a/vespalib/src/vespa/vespalib/coro/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/coro/CMakeLists.txt @@ -1,6 +1,7 @@ # Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. vespa_add_library(vespalib_vespalib_coro OBJECT SOURCES + async_crypto_socket.cpp async_io.cpp DEPENDS ) diff --git a/vespalib/src/vespa/vespalib/coro/async_crypto_socket.cpp b/vespalib/src/vespa/vespalib/coro/async_crypto_socket.cpp new file mode 100644 index 00000000000..4f862b48690 --- /dev/null +++ b/vespalib/src/vespa/vespalib/coro/async_crypto_socket.cpp @@ -0,0 +1,261 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "async_crypto_socket.h" +#include <vespa/vespalib/net/tls/protocol_snooping.h> +#include <vespa/vespalib/net/tls/tls_crypto_engine.h> +#include <vespa/vespalib/net/tls/crypto_codec.h> +#include <vespa/vespalib/data/smart_buffer.h> + +namespace vespalib::coro { + +namespace { + +using net::tls::CryptoCodec; +using net::tls::HandshakeResult; +using net::tls::EncodeResult; +using net::tls::DecodeResult; + +struct InvalidSocket : AsyncCryptoSocket { + Lazy<ssize_t> read(char *, size_t) override { co_return -EINVAL; } + Lazy<ssize_t> write(const char *, size_t) override { co_return -EINVAL; } +}; + +struct RawSocket : AsyncCryptoSocket { + AsyncIo::SP async; + SocketHandle handle; + RawSocket(AsyncIo &async_in, SocketHandle handle_in) + : async(async_in.shared_from_this()), handle(std::move(handle_in)) {} + Lazy<ssize_t> read(char *buf, size_t len) override { + return async->read(handle, buf, len); + } + Lazy<ssize_t> write(const char *buf, size_t len) override { + return async->write(handle, buf, len); + } +}; + +struct SnoopedRawSocket : AsyncCryptoSocket { + AsyncIo::SP async; + SocketHandle handle; + SmartBuffer data; + SnoopedRawSocket(AsyncIo &async_in, SocketHandle handle_in) + : async(async_in.shared_from_this()), handle(std::move(handle_in)), data(0) {} + void inject_data(const char *buf, size_t len) { + if (len > 0) { + auto dst = data.reserve(len); + memcpy(dst.data, buf, len); + data.commit(len); + } + } + Lazy<ssize_t> read_from_buffer(char *buf, size_t len) { + auto src = data.obtain(); + size_t frame = std::min(len, src.size); + if (frame > 0) { + memcpy(buf, src.data, frame); + data.evict(frame); + data.drop_if_empty(); + } + co_return frame; + } + Lazy<ssize_t> read(char *buf, size_t len) override { + if (data.empty()) { + return async->read(handle, buf, len); + } else { + return read_from_buffer(buf, len); + } + } + Lazy<ssize_t> write(const char *buf, size_t len) override { + return async->write(handle, buf, len); + } +}; + +struct TlsSocket : AsyncCryptoSocket { + AsyncIo::SP async; + SocketHandle handle; + std::unique_ptr<CryptoCodec> codec; + SmartBuffer app_input; + SmartBuffer enc_input; + SmartBuffer enc_output; + TlsSocket(AsyncIo &async_in, SocketHandle handle_in, std::unique_ptr<CryptoCodec> codec_in) + : async(async_in.shared_from_this()), handle(std::move(handle_in)), codec(std::move(codec_in)), + app_input(0), enc_input(0), enc_output(0) {} + void inject_enc_input(const char *buf, size_t len) { + if (len > 0) { + auto dst = enc_input.reserve(len); + memcpy(dst.data, buf, len); + enc_input.commit(len); + } + } + Lazy<bool> flush_enc_output() { + while (!enc_output.empty()) { + auto pending = enc_output.obtain(); + auto res = co_await async->write(handle, pending.data, pending.size); + if (res > 0) { + enc_output.evict(res); + } else { + co_return false; + } + } + co_return true; + } + Lazy<bool> fill_enc_input() { + auto dst = enc_input.reserve(codec->min_encode_buffer_size()); + ssize_t res = co_await async->read(handle, dst.data, dst.size); + if (res > 0) { + enc_input.commit(res); + co_return true; + } else { + co_return false; + } + } + Lazy<bool> handshake() { + for (;;) { + auto in = enc_input.obtain(); + auto out = enc_output.reserve(codec->min_encode_buffer_size()); + auto hs_res = codec->handshake(in.data, in.size, out.data, out.size); + enc_input.evict(hs_res.bytes_consumed); + enc_output.commit(hs_res.bytes_produced); + switch (hs_res.state) { + case ::vespalib::net::tls::HandshakeResult::State::Failed: co_return false; + case ::vespalib::net::tls::HandshakeResult::State::Done: co_return co_await flush_enc_output(); + case ::vespalib::net::tls::HandshakeResult::State::NeedsWork: + codec->do_handshake_work(); + break; + case ::vespalib::net::tls::HandshakeResult::State::NeedsMorePeerData: + bool flush_ok = co_await flush_enc_output(); + if (!flush_ok) { + co_return false; + } + bool fill_ok = co_await fill_enc_input(); + if (!fill_ok) { + co_return false; + } + } + } + } + Lazy<ssize_t> read(char *buf, size_t len) override { + while (app_input.empty()) { + auto src = enc_input.obtain(); + auto dst = app_input.reserve(codec->min_decode_buffer_size()); + auto res = codec->decode(src.data, src.size, dst.data, dst.size); + app_input.commit(res.bytes_produced); + enc_input.evict(res.bytes_consumed); + if (res.failed()) { + co_return -EIO; + } + if (res.closed()) { + co_return 0; + } + if (app_input.empty()) { + bool fill_ok = co_await fill_enc_input(); + if (!fill_ok) { + co_return -EIO; + } + } + } + auto src = app_input.obtain(); + size_t frame = std::min(len, src.size); + if (frame > 0) { + memcpy(buf, src.data, frame); + app_input.evict(frame); + } + co_return frame; + } + Lazy<ssize_t> write(const char *buf, size_t len) override { + auto dst = enc_output.reserve(codec->min_encode_buffer_size()); + auto res = codec->encode(buf, len, dst.data, dst.size); + if (res.failed) { + co_return -EIO; + } + enc_output.commit(res.bytes_produced); + bool flush_ok = co_await flush_enc_output(); + if (!flush_ok) { + co_return -EIO; + } + co_return res.bytes_consumed; + } +}; + +Lazy<AsyncCryptoSocket::UP> try_handshake(std::unique_ptr<TlsSocket> tls_socket) { + bool hs_ok = co_await tls_socket->handshake(); + if (hs_ok) { + co_return std::move(tls_socket); + } else { + co_return std::make_unique<InvalidSocket>(); + } +} + +Lazy<AsyncCryptoSocket::UP> accept_tls(AsyncIo &async, AbstractTlsCryptoEngine &crypto, SocketHandle handle) { + auto tls_codec = crypto.create_tls_server_crypto_codec(handle); + auto tls_socket = std::make_unique<TlsSocket>(async, std::move(handle), std::move(tls_codec)); + co_return co_await try_handshake(std::move(tls_socket)); +} + +Lazy<AsyncCryptoSocket::UP> accept_maybe_tls(AsyncIo &async, AbstractTlsCryptoEngine &crypto, SocketHandle handle) { + char buf[net::tls::snooping::min_header_bytes_to_observe()]; + memset(buf, 0, sizeof(buf)); + size_t snooped = 0; + while (snooped < sizeof(buf)) { + auto res = co_await async.read(handle, buf + snooped, sizeof(buf) - snooped); + if (res <= 0) { + co_return std::make_unique<InvalidSocket>(); + } + snooped += res; + } + if (net::tls::snooping::snoop_client_hello_header(buf) == net::tls::snooping::TlsSnoopingResult::ProbablyTls) { + auto tls_codec = crypto.create_tls_server_crypto_codec(handle); + auto tls_socket = std::make_unique<TlsSocket>(async, std::move(handle), std::move(tls_codec)); + tls_socket->inject_enc_input(buf, snooped); + co_return co_await try_handshake(std::move(tls_socket)); + } else { + auto plain_socket = std::make_unique<SnoopedRawSocket>(async, std::move(handle)); + plain_socket->inject_data(buf, snooped); + co_return std::move(plain_socket); + } +} + +Lazy<AsyncCryptoSocket::UP> connect_tls(AsyncIo &async, AbstractTlsCryptoEngine &crypto, SocketHandle handle, SocketSpec spec) { + auto tls_codec = crypto.create_tls_client_crypto_codec(handle, spec); + auto tls_socket = std::make_unique<TlsSocket>(async, std::move(handle), std::move(tls_codec)); + co_return co_await try_handshake(std::move(tls_socket)); +} + +} + +AsyncCryptoSocket::~AsyncCryptoSocket() = default; + +Lazy<AsyncCryptoSocket::UP> +AsyncCryptoSocket::accept(AsyncIo &async, CryptoEngine &crypto, + SocketHandle handle) +{ + if (dynamic_cast<NullCryptoEngine*>(&crypto)) { + co_return std::make_unique<RawSocket>(async, std::move(handle)); + } + if (auto *tls_engine = dynamic_cast<AbstractTlsCryptoEngine*>(&crypto)) { + if (tls_engine->always_use_tls_when_server()) { + co_return co_await accept_tls(async, *tls_engine, std::move(handle)); + } else { + co_return co_await accept_maybe_tls(async, *tls_engine, std::move(handle)); + } + } + co_return std::make_unique<InvalidSocket>(); +} + +Lazy<AsyncCryptoSocket::UP> +AsyncCryptoSocket::connect(AsyncIo &async, CryptoEngine &crypto, + SocketHandle handle, SocketSpec spec) +{ + if (dynamic_cast<NullCryptoEngine*>(&crypto)) { + (void) spec; // no SNI for plaintext sockets + co_return std::make_unique<RawSocket>(async, std::move(handle)); + } + if (auto *tls_engine = dynamic_cast<AbstractTlsCryptoEngine*>(&crypto)) { + if (tls_engine->use_tls_when_client()) { + co_return co_await connect_tls(async, *tls_engine, std::move(handle), spec); + } else { + co_return std::make_unique<RawSocket>(async, std::move(handle)); + } + } + co_return std::make_unique<InvalidSocket>(); +} + +} diff --git a/vespalib/src/vespa/vespalib/coro/async_crypto_socket.h b/vespalib/src/vespa/vespalib/coro/async_crypto_socket.h new file mode 100644 index 00000000000..7d792994a80 --- /dev/null +++ b/vespalib/src/vespa/vespalib/coro/async_crypto_socket.h @@ -0,0 +1,31 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "lazy.h" +#include "async_io.h" + +#include <vespa/vespalib/net/socket_spec.h> +#include <vespa/vespalib/net/socket_handle.h> +#include <vespa/vespalib/net/crypto_engine.h> + +#include <memory> + +namespace vespalib::coro { + +// A socket endpoint supporting async read/write with encryption + +struct AsyncCryptoSocket { + using UP = std::unique_ptr<AsyncCryptoSocket>; + + virtual Lazy<ssize_t> read(char *buf, size_t len) = 0; + virtual Lazy<ssize_t> write(const char *buf, size_t len) = 0; + virtual ~AsyncCryptoSocket(); + + static Lazy<AsyncCryptoSocket::UP> accept(AsyncIo &async, CryptoEngine &crypto, + SocketHandle handle); + static Lazy<AsyncCryptoSocket::UP> connect(AsyncIo &async, CryptoEngine &crypto, + SocketHandle handle, SocketSpec spec); +}; + +} diff --git a/vespalib/src/vespa/vespalib/coro/async_io.h b/vespalib/src/vespa/vespalib/coro/async_io.h index 72e2ef3a312..56d2aae7fdf 100644 --- a/vespalib/src/vespa/vespalib/coro/async_io.h +++ b/vespalib/src/vespa/vespalib/coro/async_io.h @@ -25,7 +25,7 @@ struct AsyncIo : std::enable_shared_from_this<AsyncIo> { AsyncIo &operator=(AsyncIo &&) = delete; virtual ~AsyncIo(); using SP = std::shared_ptr<AsyncIo>; - + // thin wrapper used by the owner to handle lifetime class Owner { private: @@ -44,13 +44,13 @@ struct AsyncIo : std::enable_shared_from_this<AsyncIo> { void fini_shutdown(); ~Owner(); }; - + // create an async_io 'runtime' static Owner create(); // implementation tag virtual vespalib::string get_impl_spec() = 0; - + // api for async io used by coroutines virtual Lazy<SocketHandle> accept(ServerSocket &server_socket) = 0; virtual Lazy<SocketHandle> connect(const SocketAddress &addr) = 0; diff --git a/vespalib/src/vespa/vespalib/coro/generator.h b/vespalib/src/vespa/vespalib/coro/generator.h index 1f1468d1d19..a28cba47e53 100644 --- a/vespalib/src/vespa/vespalib/coro/generator.h +++ b/vespalib/src/vespa/vespalib/coro/generator.h @@ -53,7 +53,7 @@ public: copy_awaiter(const copy_awaiter&) = delete; cpy_type value_cpy; }; - + public: promise_type(promise_type &&) = delete; promise_type(const promise_type &) = delete; @@ -105,10 +105,10 @@ public: return _handle.promise().result(); } }; - + private: Handle _handle; - + public: Generator(const Generator &) = delete; Generator &operator=(const Generator &) = delete; diff --git a/vespalib/src/vespa/vespalib/data/slime/external_memory.h b/vespalib/src/vespa/vespalib/data/slime/external_memory.h index 1a7dd85d15b..25b5c2765d7 100644 --- a/vespalib/src/vespa/vespalib/data/slime/external_memory.h +++ b/vespalib/src/vespa/vespalib/data/slime/external_memory.h @@ -15,7 +15,7 @@ namespace vespalib::slime { **/ struct ExternalMemory { using UP = std::unique_ptr<ExternalMemory>; - virtual Memory get() const = 0; + virtual Memory get() const = 0; virtual ~ExternalMemory() = default; }; diff --git a/vespalib/src/vespa/vespalib/data/smart_buffer.h b/vespalib/src/vespa/vespalib/data/smart_buffer.h index fc7042c5eea..5266e0f1c75 100644 --- a/vespalib/src/vespa/vespalib/data/smart_buffer.h +++ b/vespalib/src/vespa/vespalib/data/smart_buffer.h @@ -22,7 +22,7 @@ private: size_t _write_pos; const char *read_ptr() const { return (const char *)(_data.get()) + _read_pos; } - size_t read_len() const { return (_write_pos - _read_pos); } + size_t read_len() const { return (_write_pos - _read_pos); } char *write_ptr() { return (char *)(_data.get()) + _write_pos; } size_t write_len() const { return (_data.size() - _write_pos); } size_t unused() const { return (_data.size() - read_len()); } diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_btree_dictionary_read_snapshot.h b/vespalib/src/vespa/vespalib/datastore/unique_store_btree_dictionary_read_snapshot.h index b38c49ccab4..fc91c6fa292 100644 --- a/vespalib/src/vespa/vespalib/datastore/unique_store_btree_dictionary_read_snapshot.h +++ b/vespalib/src/vespa/vespalib/datastore/unique_store_btree_dictionary_read_snapshot.h @@ -17,7 +17,7 @@ private: using BTreeDictionaryType = BTreeDictionaryT; using FrozenView = typename BTreeDictionaryType::FrozenView; FrozenView _frozen_view; - + public: UniqueStoreBTreeDictionaryReadSnapshot(FrozenView frozen_view); void fill() override; diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_comparator.h b/vespalib/src/vespa/vespalib/datastore/unique_store_comparator.h index f91ebf64257..fa0e5630b74 100644 --- a/vespalib/src/vespa/vespalib/datastore/unique_store_comparator.h +++ b/vespalib/src/vespa/vespalib/datastore/unique_store_comparator.h @@ -11,7 +11,7 @@ namespace vespalib::datastore { /** - * Helper class for comparing elements in unique store. + * Helper class for comparing elements in unique store. */ template <typename EntryT> class UniqueStoreComparatorHelper { @@ -81,7 +81,7 @@ class UniqueStoreComparatorHelper<float> : public UniqueStoreFloatingPointCompar template <> class UniqueStoreComparatorHelper<double> : public UniqueStoreFloatingPointComparatorHelper<double> { }; - + /** * Compare two entries based on entry refs. * diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h index 8977fd1cce8..265478fbaf5 100644 --- a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h +++ b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h @@ -36,7 +36,7 @@ public: : UniqueStoreEntryBase(), _value() { } - + UniqueStoreSmallStringEntry(const char *value, size_t value_len, size_t array_size) : UniqueStoreEntryBase() { diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_value_filter.h b/vespalib/src/vespa/vespalib/datastore/unique_store_value_filter.h index 28c7a5ff001..5b4c078ab48 100644 --- a/vespalib/src/vespa/vespalib/datastore/unique_store_value_filter.h +++ b/vespalib/src/vespa/vespalib/datastore/unique_store_value_filter.h @@ -30,7 +30,7 @@ public: }; template <typename EntryT> -const EntryT UniqueStoreFloatingPointValueFilter<EntryT>::normalized_nan = -std::numeric_limits<EntryT>::quiet_NaN(); +const EntryT UniqueStoreFloatingPointValueFilter<EntryT>::normalized_nan = -std::numeric_limits<EntryT>::quiet_NaN(); /* * Specialized helper class for normalizing float values inserted into unique store. diff --git a/vespalib/src/vespa/vespalib/geo/zcurve.h b/vespalib/src/vespa/vespalib/geo/zcurve.h index 2efcc50d1ed..2f92b3a019b 100644 --- a/vespalib/src/vespa/vespalib/geo/zcurve.h +++ b/vespalib/src/vespa/vespalib/geo/zcurve.h @@ -32,9 +32,9 @@ public: ~BoundingBox() = default; - int64_t getzMinx() const { return _zMinx; } - int64_t getzMaxx() const { return _zMaxx; } - int64_t getzMiny() const { return _zMiny; } + int64_t getzMinx() const { return _zMinx; } + int64_t getzMaxx() const { return _zMaxx; } + int64_t getzMiny() const { return _zMiny; } int64_t getzMaxy() const { return _zMaxy; } /** diff --git a/vespalib/src/vespa/vespalib/net/selector.h b/vespalib/src/vespa/vespalib/net/selector.h index 9b278189215..6b40053abe1 100644 --- a/vespalib/src/vespa/vespalib/net/selector.h +++ b/vespalib/src/vespa/vespalib/net/selector.h @@ -13,7 +13,7 @@ namespace vespalib { /** - * Simple class used to hold events extracted from a call to epoll_wait. + * Simple class used to hold events extracted from a call to epoll_wait. **/ class EpollEvents { @@ -44,7 +44,7 @@ public: Selector() : _epoll(), _wakeup_pipe(), _events(4096) { - _epoll.add(_wakeup_pipe.get_read_fd(), nullptr, true, false); + _epoll.add(_wakeup_pipe.get_read_fd(), nullptr, true, false); } ~Selector() { _epoll.remove(_wakeup_pipe.get_read_fd()); diff --git a/vespalib/src/vespa/vespalib/stllike/hashtable.h b/vespalib/src/vespa/vespalib/stllike/hashtable.h index 55b055cddaf..4dbb138f63d 100644 --- a/vespalib/src/vespa/vespalib/stllike/hashtable.h +++ b/vespalib/src/vespa/vespalib/stllike/hashtable.h @@ -306,7 +306,7 @@ public: // This will insert unconditionally, without checking presence, and might cause duplicates. // Use at you own risk. void force_insert(Value && value); - + /// This gives faster iteration than can be achieved by the iterators. template <typename Func> void for_each(Func func) const; diff --git a/vespalib/src/vespa/vespalib/test/datastore/buffer_stats.h b/vespalib/src/vespa/vespalib/test/datastore/buffer_stats.h index 110d2354bd5..9fe77a853a8 100644 --- a/vespalib/src/vespa/vespalib/test/datastore/buffer_stats.h +++ b/vespalib/src/vespa/vespalib/test/datastore/buffer_stats.h @@ -12,7 +12,7 @@ namespace vespalib::datastore::test { */ struct BufferStats { - // elements + // elements size_t _used; size_t _hold; size_t _dead; @@ -25,7 +25,7 @@ struct BufferStats BufferStats &dead(size_t val) { _dead += val; return *this; } BufferStats &extra_used(size_t val) { _extra_used += val; return *this; } BufferStats &extra_hold(size_t val) { _extra_hold += val; return *this; } - + BufferStats &hold_to_dead(size_t val) { dec_hold(val); _dead += val; diff --git a/vespalib/src/vespa/vespalib/util/benchmark_timer.h b/vespalib/src/vespa/vespalib/util/benchmark_timer.h index d91d58069fa..b49b03a7787 100644 --- a/vespalib/src/vespa/vespalib/util/benchmark_timer.h +++ b/vespalib/src/vespa/vespalib/util/benchmark_timer.h @@ -81,7 +81,7 @@ private: for (size_t i = 0; i < 3; ++i) { timer.before(); loop.perform(loop_cnt); - timer.after(); + timer.after(); } if (timer.min_time() > 0.010) { return loop_cnt; @@ -89,12 +89,12 @@ private: } } - static double do_benchmark(const Loop &loop, size_t loop_cnt, double budget) { + static double do_benchmark(const Loop &loop, size_t loop_cnt, double budget) { vespalib::BenchmarkTimer timer(budget); while (timer.has_budget()) { timer.before(); loop.perform(loop_cnt); - timer.after(); + timer.after(); } return (timer.min_time() / double(loop_cnt)); } diff --git a/vespalib/src/vespa/vespalib/util/binary_hamming_distance.h b/vespalib/src/vespa/vespalib/util/binary_hamming_distance.h index ce8c8dacdf9..89e8e58ca1c 100644 --- a/vespalib/src/vespa/vespalib/util/binary_hamming_distance.h +++ b/vespalib/src/vespa/vespalib/util/binary_hamming_distance.h @@ -4,7 +4,7 @@ namespace vespalib { /** * Compute Hamming distance between two binary blobs - * + * * @param lhs a blob (to interpret as a bitvector with sz*8 bits) * @param rhs a blob (to interpret as a bitvector with sz*8 bits) * @param sz number of bytes in each blob diff --git a/vespalib/src/vespa/vespalib/util/exceptions.h b/vespalib/src/vespa/vespalib/util/exceptions.h index 8c470d25a5b..5d880ccf0ac 100644 --- a/vespalib/src/vespa/vespalib/util/exceptions.h +++ b/vespalib/src/vespa/vespalib/util/exceptions.h @@ -157,7 +157,7 @@ private: /** * NOTE: This function must only be called from within a catch block, * and the parameter must reference the caught exception. - * + * * Based on the run-time type of the exception, determine if it is * safe to handle this exception and continue normal program * operation. If the exception is considered safe, no additional diff --git a/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h b/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h index 0a83bfb4e60..9d6eb096162 100644 --- a/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h +++ b/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h @@ -42,7 +42,7 @@ public: PtrAndSize alloc(size_t sz) const override; void free(PtrAndSize alloc) const override; size_t resize_inplace(PtrAndSize, size_t) const override; - + // For unit test size_t get_end_offset() const noexcept { return _end_offset; } }; diff --git a/vespalib/src/vespa/vespalib/util/mmap_file_allocator_factory.h b/vespalib/src/vespa/vespalib/util/mmap_file_allocator_factory.h index ffa2b0929ca..3f4180fc7b9 100644 --- a/vespalib/src/vespa/vespalib/util/mmap_file_allocator_factory.h +++ b/vespalib/src/vespa/vespalib/util/mmap_file_allocator_factory.h @@ -24,7 +24,7 @@ class MmapFileAllocatorFactory { public: void setup(const vespalib::string &dir_name); std::unique_ptr<MemoryAllocator> make_memory_allocator(const vespalib::string& name); - + static MmapFileAllocatorFactory& instance(); }; diff --git a/vespalib/src/vespa/vespalib/util/thread_bundle.h b/vespalib/src/vespa/vespalib/util/thread_bundle.h index 252e8976544..1a78b82c1f3 100644 --- a/vespalib/src/vespa/vespalib/util/thread_bundle.h +++ b/vespalib/src/vespa/vespalib/util/thread_bundle.h @@ -68,7 +68,7 @@ struct ThreadBundle { // a thread bundle that can only run things in the current thread. static ThreadBundle &trivial(); - + private: Runnable *resolve(Runnable *target) { return target; } Runnable *resolve(Runnable &target) { return ⌖ } diff --git a/vespalog/src/test/bufferedlogskiptest.cpp b/vespalog/src/test/bufferedlogskiptest.cpp index ac0bb585be9..8b7f1982678 100644 --- a/vespalog/src/test/bufferedlogskiptest.cpp +++ b/vespalog/src/test/bufferedlogskiptest.cpp @@ -75,7 +75,7 @@ main(int argc, char **argv) } ns_log::Logger::fakePid = true; uint64_t timer; - logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer))); + ns_log_logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer))); ns_log::BufferedLogger::instance().setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer))); reset(timer); diff --git a/vespalog/src/test/bufferedlogtest.cpp b/vespalog/src/test/bufferedlogtest.cpp index 8399fa81dfa..365f8fb85a7 100644 --- a/vespalog/src/test/bufferedlogtest.cpp +++ b/vespalog/src/test/bufferedlogtest.cpp @@ -386,7 +386,7 @@ main(int argc, char **argv) ns_log::Logger::fakePid = true; ns_log::BufferedLogger::instance().setMaxCacheSize(10); uint64_t timer; - logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer))); + ns_log_logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer))); ns_log::BufferedLogger::instance().setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer))); reset(timer); diff --git a/vespalog/src/vespa/log/bufferedlogger.h b/vespalog/src/vespa/log/bufferedlogger.h index 373f81b5160..8baa32445a5 100644 --- a/vespalog/src/vespa/log/bufferedlogger.h +++ b/vespalog/src/vespa/log/bufferedlogger.h @@ -91,13 +91,13 @@ #ifdef VESPA_LOG_USELOGBUFFERFORREGULARLOG #define LOG(level, ...) \ do { \ - if (logger.wants(ns_log::Logger::level)) { \ - if (logger.wants(ns_log::Logger::debug)) { \ - logger.doLog(ns_log::Logger::level, \ + if (LOG_WOULD_LOG(level)) { \ + if (LOG_WOULD_LOG(debug)) { \ + ns_log_logger.doLog(ns_log::Logger::level, \ __FILE__, __LINE__, __VA_ARGS__); \ ns_log::BufferedLogger::instance().trimCache(); \ } else { \ - ns_log::BufferedLogger::instance().doLog(logger, \ + ns_log::BufferedLogger::instance().doLog(ns_log_logger, \ ns_log::Logger::level, __FILE__, __LINE__, \ "", __VA_ARGS__); \ } \ @@ -110,13 +110,13 @@ // VESPA_LOG_USELOGBUFFERFORREGULARLOG is defined. #define LOGBM(level, ...) \ do { \ - if (logger.wants(ns_log::Logger::level)) { \ - if (logger.wants(ns_log::Logger::debug)) { \ - logger.doLog(ns_log::Logger::level, \ + if (LOG_WOULD_LOG(level)) { \ + if (LOG_WOULD_LOG(debug)) { \ + ns_log_logger.doLog(ns_log::Logger::level, \ __FILE__, __LINE__, __VA_ARGS__); \ ns_log::BufferedLogger::instance().trimCache(); \ } else { \ - ns_log::BufferedLogger::instance().doLog(logger, \ + ns_log::BufferedLogger::instance().doLog(ns_log_logger, \ ns_log::Logger::level, __FILE__, __LINE__, \ "", __VA_ARGS__); \ } \ @@ -127,15 +127,15 @@ // (File/line of macro caller) #define LOGBP(level, ARGS...) \ do { \ - if (logger.wants(ns_log::Logger::level)) { \ - if (logger.wants(ns_log::Logger::debug)) { \ - logger.doLog(ns_log::Logger::level, \ + if (LOG_WOULD_LOG(level)) { \ + if (LOG_WOULD_LOG(debug)) { \ + ns_log_logger.doLog(ns_log::Logger::level, \ __FILE__, __LINE__, ##ARGS); \ ns_log::BufferedLogger::instance().trimCache(); \ } else { \ std::ostringstream ost123; \ ost123 << __FILE__ << ":" << __LINE__; \ - ns_log::BufferedLogger::instance().doLog(logger, \ + ns_log::BufferedLogger::instance().doLog(ns_log_logger, \ ns_log::Logger::level, \ __FILE__, __LINE__, ost123.str(), ##ARGS); \ } \ @@ -145,13 +145,13 @@ // Define LOGT calls for using the buffer specifically stating token #define LOGBT(level, token, ...) \ do { \ - if (logger.wants(ns_log::Logger::level)) { \ - if (logger.wants(ns_log::Logger::debug)) { \ - logger.doLog(ns_log::Logger::level, \ + if (LOG_WOULD_LOG(level)) { \ + if (LOG_WOULD_LOG(debug)) { \ + ns_log_logger.doLog(ns_log::Logger::level, \ __FILE__, __LINE__, __VA_ARGS__); \ ns_log::BufferedLogger::instance().trimCache(); \ } else { \ - ns_log::BufferedLogger::instance().doLog(logger, \ + ns_log::BufferedLogger::instance().doLog(ns_log_logger, \ ns_log::Logger::level, \ __FILE__, __LINE__, token, __VA_ARGS__); \ } \ diff --git a/vespalog/src/vespa/log/log.cpp b/vespalog/src/vespa/log/log.cpp index 7f2668a97ce..69d69b97874 100644 --- a/vespalog/src/vespa/log/log.cpp +++ b/vespalog/src/vespa/log/log.cpp @@ -175,19 +175,19 @@ Logger::Logger(const char *name, const char *rcsId) Logger::~Logger() { - _numInstances--; - if (_numInstances == 1) { - if (logger != nullptr) { - logger->~Logger(); - free(logger); - logger = nullptr; + _numInstances--; + if (_numInstances == 1) { + if (ns_log_indirect_logger != nullptr) { + ns_log_indirect_logger->~Logger(); + free(ns_log_indirect_logger); + ns_log_indirect_logger = nullptr; + } + } else if (_numInstances == 0) { + delete _controlFile; + logInitialised = false; + delete _target; + _target = nullptr; } - } else if (_numInstances == 0) { - delete _controlFile; - logInitialised = false; - delete _target; - _target = nullptr; - } } @@ -406,4 +406,13 @@ Logger::doEventState(const char *name, const char *value) doLog(event, "", 0, "state/1 name=\"%s\" value=\"%s\"", name, value); } +LogTarget * +Logger::getCurrentTarget() +{ + if (_target == nullptr) { + throwInvalid("No current log target"); + } + return _target; +} + } // end namespace ns_log diff --git a/vespalog/src/vespa/log/log.h b/vespalog/src/vespa/log/log.h index 5b73aef5083..22b6a720bd2 100644 --- a/vespalog/src/vespa/log/log.h +++ b/vespalog/src/vespa/log/log.h @@ -21,31 +21,37 @@ // Used to use anonymous namespaces, but they fail miserably in gdb 5.3 #define LOG_SETUP(...) \ -static ns_log::Logger logger(__VA_ARGS__) // NOLINT +static ns_log::Logger ns_log_logger(__VA_ARGS__) // NOLINT -#define LOG_SETUP_INDIRECT(x, id) \ -static ns_log::Logger *logger=NULL; \ -static bool logInitialised = false; \ -static const char *logName = x; \ +#define LOG_SETUP_INDIRECT(x, id) \ +static ns_log::Logger *ns_log_indirect_logger=NULL; \ +static bool logInitialised = false; \ +static const char *logName = x; \ static const char *indirectRcsId = id +#define LOG_WOULD_LOG(level) ns_log_logger.wants(ns_log::Logger::level) +#define LOG_WOULD_VLOG(level) ns_log_logger.wants(level) +#define LOG_INDIRECT_WOULD_LOG(levelName) \ + ns_log_indirect_logger->wants(ns_log::Logger::levelName) #define LOG_RCSID(x) \ -static int log_dummmy __attribute__((unused)) = logger.setRcsId(x) - +static int log_dummmy __attribute__((unused)) = ns_log_logger.setRcsId(x) // Define LOG if not using log buffer. Otherwise log buffer will define them #ifndef VESPA_LOG_USELOGBUFFERFORREGULARLOG -#define LOG(level, ...) \ -do { \ - if (__builtin_expect(logger.wants(ns_log::Logger::level), false)) { \ - logger.doLog(ns_log::Logger::level, __FILE__, __LINE__, __VA_ARGS__); \ - } \ +#define LOG(level, ...) \ +do { \ + if (__builtin_expect(LOG_WOULD_LOG(level), false)) { \ + ns_log_logger.doLog(ns_log::Logger::level, \ + __FILE__, __LINE__, __VA_ARGS__); \ + } \ } while (false) + #define VLOG(level, ...) \ do { \ - if (__builtin_expect(logger.wants(level), false)) { \ - logger.doLog(level, __FILE__, __LINE__, __VA_ARGS__); \ + if (__builtin_expect(LOG_WOULD_VLOG(level), false)) { \ + ns_log_logger.doLog(level, \ + __FILE__, __LINE__, __VA_ARGS__); \ } \ } while (false) #endif @@ -53,84 +59,85 @@ do { \ // Must use placement new in the following definition, since the variable // "logger" must be a valid logger object DURING the construction of the // logger object itself. -#define LOG_INDIRECT_MUST \ - if (!logInitialised) { \ - logInitialised = true; \ - logger = static_cast<Logger *>(malloc(sizeof *logger)); \ - new (logger) Logger(logName, indirectRcsId); \ - } -#define LOG_INDIRECT(level, ...) \ -do { \ - LOG_INDIRECT_MUST \ - if (logger->wants(ns_log::Logger::level)) { \ - logger->doLog(ns_log::Logger::level, __FILE__, __LINE__, __VA_ARGS__); \ - } \ +#define LOG_INDIRECT(level, ...) \ +do { \ + if (!logInitialised) { \ + logInitialised = true; \ + ns_log_indirect_logger = \ + static_cast<Logger *>( \ + malloc(sizeof *ns_log_indirect_logger)); \ + new (ns_log_indirect_logger) Logger(logName, indirectRcsId); \ + } \ + if (LOG_INDIRECT_WOULD_LOG(level)) { \ + ns_log_indirect_logger->doLog(ns_log::Logger::level, \ + __FILE__, __LINE__, __VA_ARGS__); \ + } \ } while (false) -#define LOG_WOULD_LOG(level) logger.wants(ns_log::Logger::level) -#define LOG_WOULD_VLOG(level) logger.wants(level) -#define EV_STARTING(name) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventStarting(name); \ - } \ +#define EV_STARTING(name) \ +do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventStarting(name); \ + } \ } while (false) -#define EV_STOPPING(name,why) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventStopping(name, why); \ - } \ +#define EV_STOPPING(name,why) \ +do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventStopping(name, why); \ + } \ } while (false) #define EV_STARTED(name) \ do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventStarted(name); \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventStarted(name); \ } \ } while (false) -#define EV_STOPPED(name,pid,exitcode) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventStopped(name, pid, exitcode); \ - } \ -} while (false) - -#define EV_CRASH(name,pid,signal) \ +#define EV_STOPPED(name,pid,exitcode) \ do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventCrash(name, pid, signal); \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventStopped(name, pid, \ + exitcode); \ } \ } while (false) -#define EV_PROGRESS(name, ...) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventProgress(name, __VA_ARGS__); \ - } \ +#define EV_CRASH(name,pid,signal) \ +do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventCrash(name, pid, signal); \ + } \ } while (false) -#define EV_COUNT(name,value) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventCount(name, value); \ - } \ +#define EV_PROGRESS(name, ...) \ +do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventProgress(name, \ + __VA_ARGS__); \ + } \ } while (false) -#define EV_VALUE(name,value) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventValue(name, value); \ - } \ +#define EV_COUNT(name,value) \ + do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventCount(name, value); \ + } \ } while (false) -#define EV_STATE(name,value) \ -do { \ - if (logger.wants(ns_log::Logger::event)) { \ - logger.doEventState(name, value); \ - } \ +#define EV_VALUE(name,value) \ +do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventValue(name, value); \ + } \ +} while (false) + +#define EV_STATE(name,value) \ +do { \ + if (LOG_WOULD_LOG(event)) { \ + ns_log_logger.doEventState(name, value); \ + } \ } while (false) namespace ns_log { @@ -228,6 +235,9 @@ public: // Only for unit testing void setTimer(std::unique_ptr<Timer> timer) { _timer = std::move(timer); } + + // Only for internal use + static LogTarget *getCurrentTarget(); }; |