diff options
author | Lester Solbakken <lesters@users.noreply.github.com> | 2021-05-27 10:41:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-27 10:41:31 +0200 |
commit | e3d4dbac364216f8d93493d4a5f34835a268fbcf (patch) | |
tree | 90bc2cf28e08123a55854c2db1217f556d349a2e | |
parent | 92efe91ec3d7be1902e7ca9c0e290c7859d535af (diff) | |
parent | 6b6e59869ab5259a8cd2e382cd2b5164a963a293 (diff) |
Merge branch 'master' into lesters/wire-in-stateless-onnx-rt
284 files changed, 3498 insertions, 1543 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c98994cf993..7bf698b7a98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,9 @@ find_package(JNI REQUIRED) find_package(GTest REQUIRED) +set(PYBIND11_FIND_PYTHON ON) +find_package(pybind11 CONFIG REQUIRED) + include(build_settings.cmake) # Enable CTest unit testing @@ -42,6 +45,7 @@ vespa_install_data(valgrind-suppressions.txt etc/vespa) # Include vespa config definitions in every target include_directories(BEFORE ${CMAKE_BINARY_DIR}/configdefinitions/src) +add_subdirectory(ann_benchmark) add_subdirectory(application-model) add_subdirectory(application-preprocessor) add_subdirectory(athenz-identity-provider-service) @@ -132,6 +136,7 @@ add_subdirectory(vdslib) add_subdirectory(vdstestlib) add_subdirectory(vespa-athenz) add_subdirectory(vespa-feed-client) +add_subdirectory(vespa-feed-client-cli) add_subdirectory(vespa-http-client) add_subdirectory(vespa-osgi-testrunner) add_subdirectory(vespa-testrunner-components) diff --git a/ann_benchmark/CMakeLists.txt b/ann_benchmark/CMakeLists.txt new file mode 100644 index 00000000000..d34329667e3 --- /dev/null +++ b/ann_benchmark/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_define_module( + DEPENDS + searchlib + + LIBS + src/vespa/ann_benchmark + + APPS + + TESTS + src/tests/ann_benchmark +) diff --git a/ann_benchmark/src/tests/ann_benchmark/.gitignore b/ann_benchmark/src/tests/ann_benchmark/.gitignore new file mode 100644 index 00000000000..225fc6f6650 --- /dev/null +++ b/ann_benchmark/src/tests/ann_benchmark/.gitignore @@ -0,0 +1 @@ +/__pycache__ diff --git a/ann_benchmark/src/tests/ann_benchmark/CMakeLists.txt b/ann_benchmark/src/tests/ann_benchmark/CMakeLists.txt new file mode 100644 index 00000000000..6553cd071fb --- /dev/null +++ b/ann_benchmark/src/tests/ann_benchmark/CMakeLists.txt @@ -0,0 +1,3 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +vespa_add_test(NAME ann_benchmark_test NO_VALGRIND COMMAND ${PYTHON_EXECUTABLE} -m pytest WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS vespa_ann_benchmark) diff --git a/ann_benchmark/src/tests/ann_benchmark/test_angular.py b/ann_benchmark/src/tests/ann_benchmark/test_angular.py new file mode 100644 index 00000000000..3e48a6bd970 --- /dev/null +++ b/ann_benchmark/src/tests/ann_benchmark/test_angular.py @@ -0,0 +1,41 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +import pytest +import sys +import os +import math +sys.path.insert(0, os.path.abspath("../../vespa/ann_benchmark")) +from vespa_ann_benchmark import DistanceMetric, HnswIndexParams, HnswIndex + +class Fixture: + def __init__(self, normalize): + metric = DistanceMetric.InnerProduct if normalize else DistanceMetric.Angular + self.index = HnswIndex(2, HnswIndexParams(16, 200, metric, False), normalize) + self.index.set_vector(0, [1, 0]) + self.index.set_vector(1, [10, 10]) + + def find(self, k, value): + return self.index.find_top_k(k, value, k + 200) + + def run_test(self): + top = self.find(10, [1, 1]) + assert [top[0][0], top[1][0]] == [0, 1] + # Allow some rounding errors + epsilon = 5e-8 + assert abs((1 - top[0][1]) - math.sqrt(0.5)) < epsilon + assert abs((1 - top[1][1]) - 1) < epsilon + top2 = self.find(10, [0, 2]) + # Result is not sorted by distance + assert [top2[0][0], top2[1][0]] == [0, 1] + assert abs((1 - top2[0][1]) - 0) < epsilon + assert abs((1 - top2[1][1]) - math.sqrt(0.5)) < epsilon + assert 1 == self.find(1, [1, 1])[0][0] + assert 0 == self.find(1, [1, -1])[0][0] + +def test_find_angular(): + f = Fixture(False) + f.run_test() + +def test_find_angular_normalized(): + f = Fixture(True) + f.run_test() diff --git a/ann_benchmark/src/tests/ann_benchmark/test_euclidean.py b/ann_benchmark/src/tests/ann_benchmark/test_euclidean.py new file mode 100644 index 00000000000..b6fb06cb029 --- /dev/null +++ b/ann_benchmark/src/tests/ann_benchmark/test_euclidean.py @@ -0,0 +1,61 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +import pytest +import sys +import os +import math +sys.path.insert(0, os.path.abspath("../../vespa/ann_benchmark")) +from vespa_ann_benchmark import DistanceMetric, HnswIndexParams, HnswIndex + +class Fixture: + def __init__(self): + self.index = HnswIndex(2, HnswIndexParams(16, 200, DistanceMetric.Euclidean, False), False) + + def set(self, lid, value): + self.index.set_vector(lid, value) + + def get(self, lid): + return self.index.get_vector(lid) + + def clear(self, lid): + return self.index.clear_vector(lid) + + def find(self, k, value): + return self.index.find_top_k(k, value, k + 200) + +def test_set_value(): + f = Fixture() + f.set(0, [1, 2]) + f.set(1, [3, 4]) + assert [1, 2] == f.get(0) + assert [3, 4] == f.get(1) + +def test_clear_value(): + f = Fixture() + f.set(0, [1, 2]) + assert [1, 2] == f.get(0) + f.clear(0) + assert [0, 0] == f.get(0) + +def test_find(): + f = Fixture() + f.set(0, [0, 0]) + f.set(1, [10, 10]) + top = f.find(10, [1, 1]) + assert [top[0][0], top[1][0]] == [0, 1] + # Allow some rounding errors + epsilon = 1e-20 + assert abs(top[0][1] - math.sqrt(2)) < epsilon + assert abs(top[1][1] - math.sqrt(162)) < epsilon + top2 = f.find(10, [9, 9]) + # Result is not sorted by distance + assert [top2[0][0], top2[1][0]] == [0, 1] + assert abs(top2[0][1] - math.sqrt(162)) < epsilon + assert abs(top2[1][1] - math.sqrt(2)) < epsilon + assert 0 == f.find(1, [1, 1])[0][0] + assert 1 == f.find(1, [9, 9])[0][0] + f.clear(1) + assert 0 == f.find(1, [9, 9])[0][0] + assert 0 == f.find(1, [0, 0])[0][0] + f.clear(0) + assert 0 == len(f.find(1, [9, 9])) diff --git a/ann_benchmark/src/vespa/ann_benchmark/.gitignore b/ann_benchmark/src/vespa/ann_benchmark/.gitignore new file mode 100644 index 00000000000..8249d30d8c7 --- /dev/null +++ b/ann_benchmark/src/vespa/ann_benchmark/.gitignore @@ -0,0 +1 @@ +/vespa_ann_benchmark.cpython-36m-x86_64-linux-gnu.so diff --git a/ann_benchmark/src/vespa/ann_benchmark/CMakeLists.txt b/ann_benchmark/src/vespa/ann_benchmark/CMakeLists.txt new file mode 100644 index 00000000000..a9a8ceb3660 --- /dev/null +++ b/ann_benchmark/src/vespa/ann_benchmark/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +install(DIRECTORY DESTINATION libexec/vespa_ann_benchmark) + +vespa_add_library(vespa_ann_benchmark + ALLOW_UNRESOLVED_SYMBOLS + SOURCES + vespa_ann_benchmark.cpp + + INSTALL libexec/vespa_ann_benchmark + DEPENDS + pybind11::pybind11 +) + +if (TARGET pybind11::lto) + target_link_libraries(vespa_ann_benchmark PRIVATE pybind11::module pybind11::lto) +else() + target_link_libraries(vespa_ann_benchmark PRIVATE pybind11::module) +endif() + +if (COMMAND pybind11_extension) + pybind11_extension(vespa_ann_benchmark) +else() + set_target_properties(vespa_ann_benchmark PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}") + set_target_properties(vespa_ann_benchmark PROPERTIES SUFFIX "${PYTHON_MODULE_EXTENSION}") +endif() + +set_target_properties(vespa_ann_benchmark PROPERTIES CXX_VISIBILITY_PRESET "hidden") +vespa_install_script(setup.py libexec/vespa_ann_benchmark) diff --git a/ann_benchmark/src/vespa/ann_benchmark/setup.py b/ann_benchmark/src/vespa/ann_benchmark/setup.py new file mode 100644 index 00000000000..e19dada5fff --- /dev/null +++ b/ann_benchmark/src/vespa/ann_benchmark/setup.py @@ -0,0 +1,27 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +import os +import sys +import platform +import distutils.sysconfig +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext + +class PreBuiltExt(build_ext): + def build_extension(self, ext): + print("Using prebuilt extension library") + libdir="lib.%s-%s-%s" % (sys.platform, platform.machine(), distutils.sysconfig.get_python_version()) + os.system("mkdir -p build/%s" % libdir) + os.system("cp -p vespa_ann_benchmark.*.so build/%s" % libdir) + +setup( + name="vespa_ann_benchmark", + version="0.1.0", + author="Tor Egge", + author_email="Tor.Egge@verizonmedia.com", + description="Python binding for the Vespa implementation of an HNSW index for nearest neighbor search", + long_description="Python binding for the Vespa implementation of an HNSW index for nearest neighbor search used for low-level benchmarking", + ext_modules=[Extension("vespa_ann_benchmark", sources=[])], + cmdclass={"build_ext": PreBuiltExt}, + zip_safe=False, +) diff --git a/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp b/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp new file mode 100644 index 00000000000..3304e598862 --- /dev/null +++ b/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp @@ -0,0 +1,246 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include <vespa/searchcommon/attribute/hnsw_index_params.h> +#include <vespa/searchlib/attribute/attributevector.h> +#include <vespa/searchlib/attribute/attributefactory.h> +#include <vespa/searchlib/tensor/dense_tensor_attribute.h> +#include <vespa/searchlib/tensor/nearest_neighbor_index.h> +#include <vespa/eval/eval/value.h> +#include <vespa/vespalib/test/insertion_operators.h> +#include <ostream> +#include <sstream> +#include <limits> + +namespace py = pybind11; + +using search::AttributeFactory; +using search::AttributeVector; +using search::attribute::BasicType; +using search::attribute::Config; +using search::attribute::CollectionType; +using search::attribute::DistanceMetric; +using search::attribute::HnswIndexParams; +using search::tensor::NearestNeighborIndex; +using search::tensor::TensorAttribute; +using vespalib::eval::CellType; +using vespalib::eval::DenseValueView; +using vespalib::eval::TypedCells; +using vespalib::eval::ValueType; +using vespalib::eval::Value; + +namespace vespa_ann_benchmark { + +using TopKResult = std::vector<std::pair<uint32_t, double>>; + +namespace { + +std::string +make_tensor_spec(uint32_t dim_size) +{ + std::ostringstream os; + os << "tensor<float>(x[" << dim_size << "])"; + return os.str(); +} + +constexpr uint32_t lid_bias = 1; // lid 0 is reserved + +} + +/* + * Class exposing the Vespa implementation of an HNSW index for nearest neighbor search over data points in a high dimensional vector space. + * + * A tensor attribute field (https://docs.vespa.ai/en/reference/schema-reference.html#type:tensor) is used to store the vectors in memory. + * This class only supports single-threaded access (both for indexing and searching), + * and should only be used for low-level benchmarking. + * To use nearest neighbor search in a Vespa application, + * see https://docs.vespa.ai/en/approximate-nn-hnsw.html for more details. + */ +class HnswIndex +{ + ValueType _tensor_type; + HnswIndexParams _hnsw_index_params; + std::shared_ptr<AttributeVector> _attribute; + TensorAttribute* _tensor_attribute; + const NearestNeighborIndex* _nearest_neighbor_index; + size_t _dim_size; + bool _normalize_vectors; + + bool check_lid(uint32_t lid); + bool check_value(const char *op, const std::vector<float>& value); + TypedCells get_typed_cells(const std::vector<float>& value, std::vector<float>& normalized_value); +public: + HnswIndex(uint32_t dim_size, const HnswIndexParams &hnsw_index_params, bool normalize_vectors); + virtual ~HnswIndex(); + void set_vector(uint32_t lid, const std::vector<float>& value); + std::vector<float> get_vector(uint32_t lid); + void clear_vector(uint32_t lid); + TopKResult find_top_k(uint32_t k, const std::vector<float>& value, uint32_t explore_k); +}; + +HnswIndex::HnswIndex(uint32_t dim_size, const HnswIndexParams &hnsw_index_params, bool normalize_vectors) + : _tensor_type(ValueType::error_type()), + _hnsw_index_params(hnsw_index_params), + _attribute(), + _tensor_attribute(nullptr), + _nearest_neighbor_index(nullptr), + _dim_size(0u), + _normalize_vectors(normalize_vectors) +{ + Config cfg(BasicType::TENSOR, CollectionType::SINGLE); + _tensor_type = ValueType::from_spec(make_tensor_spec(dim_size)); + assert(_tensor_type.is_dense()); + assert(_tensor_type.count_indexed_dimensions() == 1u); + _dim_size = _tensor_type.dimensions()[0].size; + cfg.setTensorType(_tensor_type); + cfg.set_distance_metric(hnsw_index_params.distance_metric()); + cfg.set_hnsw_index_params(hnsw_index_params); + _attribute = AttributeFactory::createAttribute("tensor", cfg); + _tensor_attribute = dynamic_cast<TensorAttribute *>(_attribute.get()); + assert(_tensor_attribute != nullptr); + _nearest_neighbor_index = _tensor_attribute->nearest_neighbor_index(); + assert(_nearest_neighbor_index != nullptr); +} + +HnswIndex::~HnswIndex() = default; + +bool +HnswIndex::check_lid(uint32_t lid) +{ + if (lid >= std::numeric_limits<uint32_t>::max() - lid_bias) { + std::cerr << "lid is too high" << std::endl; + return false; + } + return true; +} + +bool +HnswIndex::check_value(const char *op, const std::vector<float>& value) +{ + if (value.size() != _dim_size) { + std::cerr << op << " failed, expected vector with size " << _dim_size << ", got vector with size " << value.size() << std::endl; + return false; + } + return true; +} + +TypedCells +HnswIndex::get_typed_cells(const std::vector<float>& value, std::vector<float>& normalized_value) +{ + if (!_normalize_vectors) { + return {&value[0], CellType::FLOAT, value.size()}; + } + float sum_of_squared = 0.0f; + for (auto elem : value) { + sum_of_squared += elem * elem; + } + float factor = 1.0f / (sqrtf(sum_of_squared) + 1e-40f); + normalized_value.reserve(value.size()); + normalized_value.clear(); + for (auto elem : value) { + normalized_value.emplace_back(elem * factor); + } + return {&normalized_value[0], CellType::FLOAT, normalized_value.size()}; +} + +void +HnswIndex::set_vector(uint32_t lid, const std::vector<float>& value) +{ + if (!check_lid(lid)) { + return; + } + if (!check_value("set_vector", value)) { + return; + } + /* + * Not thread safe against concurrent set_vector(). + */ + std::vector<float> normalized_value; + auto typed_cells = get_typed_cells(value, normalized_value); + DenseValueView tensor_view(_tensor_type, typed_cells); + while (size_t(lid + lid_bias) >= _attribute->getNumDocs()) { + uint32_t new_lid = 0; + _attribute->addDoc(new_lid); + } + _tensor_attribute->setTensor(lid + lid_bias, tensor_view); // lid 0 is special in vespa + _attribute->commit(); +} + +std::vector<float> +HnswIndex::get_vector(uint32_t lid) +{ + if (!check_lid(lid)) { + return {}; + } + TypedCells typed_cells = _tensor_attribute->extract_cells_ref(lid + lid_bias); + assert(typed_cells.size == _dim_size); + const float* data = static_cast<const float* >(typed_cells.data); + return {data, data + _dim_size}; + return {}; +} + +void +HnswIndex::clear_vector(uint32_t lid) +{ + if (!check_lid(lid)) { + return; + } + if (size_t(lid + lid_bias) < _attribute->getNumDocs()) { + _attribute->clearDoc(lid + lid_bias); + _attribute->commit(); + } +} + +TopKResult +HnswIndex::find_top_k(uint32_t k, const std::vector<float>& value, uint32_t explore_k) +{ + if (!check_value("find_top_k", value)) { + return {}; + } + /* + * Not thread safe against concurrent set_vector() since attribute + * read guard is not taken here. + */ + TopKResult result; + std::vector<float> normalized_value; + auto typed_cells = get_typed_cells(value, normalized_value); + auto raw_result = _nearest_neighbor_index->find_top_k(k, typed_cells, explore_k, std::numeric_limits<double>::max()); + result.reserve(raw_result.size()); + switch (_hnsw_index_params.distance_metric()) { + case DistanceMetric::Euclidean: + for (auto &raw : raw_result) { + result.emplace_back(raw.docid - lid_bias, sqrt(raw.distance)); + } + break; + default: + for (auto &raw : raw_result) { + result.emplace_back(raw.docid - lid_bias, raw.distance); + } + } + // Results are sorted by lid, not by distance + return result; +} + +} + +using vespa_ann_benchmark::HnswIndex; + +PYBIND11_MODULE(vespa_ann_benchmark, m) { + m.doc() = "vespa_ann_benchmark plugin"; + + py::enum_<DistanceMetric>(m, "DistanceMetric") + .value("Euclidean", DistanceMetric::Euclidean) + .value("Angular", DistanceMetric::Angular) + .value("InnerProduct", DistanceMetric::InnerProduct); + + py::class_<HnswIndexParams>(m, "HnswIndexParams") + .def(py::init<uint32_t, uint32_t, DistanceMetric, bool>()); + + py::class_<HnswIndex>(m, "HnswIndex") + .def(py::init<uint32_t, const HnswIndexParams&, bool>()) + .def("set_vector", &HnswIndex::set_vector) + .def("get_vector", &HnswIndex::get_vector) + .def("clear_vector", &HnswIndex::clear_vector) + .def("find_top_k", &HnswIndex::find_top_k); +} diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java index b29e232dc08..ff27576940f 100644 --- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java +++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java @@ -2,10 +2,13 @@ package com.yahoo.vespa.clustercontroller.core; import com.yahoo.jrt.ErrorCode; -import java.util.logging.Level; -import com.yahoo.vdslib.state.*; +import com.yahoo.vdslib.state.ClusterState; +import com.yahoo.vdslib.state.Node; +import com.yahoo.vdslib.state.NodeState; +import com.yahoo.vdslib.state.State; import com.yahoo.vespa.clustercontroller.core.database.DatabaseHandler; +import java.util.logging.Level; import java.util.LinkedList; import java.util.List; import java.util.Map; diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/AppSubDirs.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/AppSubDirs.java index 2b7aa5ab6e1..ca8f9c17066 100644 --- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/AppSubDirs.java +++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/AppSubDirs.java @@ -13,22 +13,14 @@ import java.io.File; */ public class AppSubDirs { - final Tuple2<File, String> root; - public final Tuple2<File, String> rules; - final Tuple2<File, String> searchchains; - final Tuple2<File, String> docprocchains; - final Tuple2<File, String> routingtables; - final Tuple2<File, String> configDefs; - final Tuple2<File, String> searchdefinitions; + private final Tuple2<File, String> root; + private final Tuple2<File, String> routingtables; + private final Tuple2<File, String> configDefs; public AppSubDirs(File root) { this.root = new Tuple2<>(root, root.getName()); - rules = createTuple(ApplicationPackage.RULES_DIR.getRelative()); - searchchains = createTuple(ApplicationPackage.SEARCHCHAINS_DIR); - docprocchains = createTuple(ApplicationPackage.DOCPROCCHAINS_DIR); routingtables = createTuple(ApplicationPackage.ROUTINGTABLES_DIR); configDefs = createTuple(ApplicationPackage.CONFIG_DEFINITIONS_DIR); - searchdefinitions = createTuple(ApplicationPackage.SEARCH_DEFINITIONS_DIR.getRelative()); } private Tuple2<File, String> createTuple(String name) { @@ -43,23 +35,11 @@ public class AppSubDirs { return root.first; } - public File rules() { - return rules.first; - } - - public File searchchains() { - return searchchains.first; - } - - public File docprocchains() { - return docprocchains.first; - } - public File configDefs() { return configDefs.first; } - - @Deprecated // Remove after March 2020 - public File searchdefinitions() { return searchdefinitions.first; } + public Tuple2<File, String> routingTables() { + return routingtables; + } } diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/ApplicationPackageXmlFilesValidator.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/ApplicationPackageXmlFilesValidator.java index b0c4f74f9f6..79a8c6ea16c 100644 --- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/ApplicationPackageXmlFilesValidator.java +++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/ApplicationPackageXmlFilesValidator.java @@ -40,7 +40,7 @@ public class ApplicationPackageXmlFilesValidator { validateOptional(validators.hostsXmlValidator(), FilesApplicationPackage.HOSTS); validateOptional(validators.deploymentXmlValidator(), FilesApplicationPackage.DEPLOYMENT_FILE.getName()); validateOptional(validators.validationOverridesXmlValidator(), FilesApplicationPackage.VALIDATION_OVERRIDES.getName()); - validateRouting(appDirs.routingtables); + validateRouting(appDirs.routingTables()); } // For testing diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java index da5bff984db..db875f669fe 100644 --- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java +++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java @@ -148,14 +148,13 @@ public class FilesApplicationPackage implements ApplicationPackage { * @param metaData metadata for this application package * @param includeSourceFiles include files from source dirs */ - @SuppressWarnings("deprecation") private FilesApplicationPackage(File appDir, File preprocessedDir, ApplicationMetaData metaData, boolean includeSourceFiles) { verifyAppDir(appDir); this.includeSourceFiles = includeSourceFiles; this.appDir = appDir; this.preprocessedDir = preprocessedDir; appSubDirs = new AppSubDirs(appDir); - configDefsDir = new File(appDir, ApplicationPackage.CONFIG_DEFINITIONS_DIR); + configDefsDir = new File(appDir, CONFIG_DEFINITIONS_DIR); addUserIncludeDirs(); this.metaData = metaData; transformerFactory = TransformerFactory.newInstance(); @@ -236,9 +235,8 @@ public class FilesApplicationPackage implements ApplicationPackage { return getHostsFile().getPath(); } - @SuppressWarnings("deprecation") private File getHostsFile() { - return new File(appDir, ApplicationPackage.HOSTS); + return new File(appDir, HOSTS); } @Override @@ -246,9 +244,8 @@ public class FilesApplicationPackage implements ApplicationPackage { return getServicesFile().getPath(); } - @SuppressWarnings("deprecation") private File getServicesFile() { - return new File(appDir, ApplicationPackage.SERVICES); + return new File(appDir, SERVICES); } @Override @@ -338,7 +335,7 @@ public class FilesApplicationPackage implements ApplicationPackage { addAllDefsFromConfigDir(defs, new File("src/main/resources/configdefinitions")); addAllDefsFromConfigDir(defs, new File("src/test/resources/configdefinitions")); } - addAllDefsFromBundles(defs, FilesApplicationPackage.getComponents(appDir)); + addAllDefsFromBundles(defs, getComponents(appDir)); return defs; } @@ -427,13 +424,13 @@ public class FilesApplicationPackage implements ApplicationPackage { static List<File> getSearchDefinitionFiles(File appDir) { List<File> schemaFiles = new ArrayList<>(); - File sdDir = new File(appDir, ApplicationPackage.SEARCH_DEFINITIONS_DIR.getRelative()); + File sdDir = new File(appDir, SEARCH_DEFINITIONS_DIR.getRelative()); if (sdDir.isDirectory()) - schemaFiles.addAll(Arrays.asList(sdDir.listFiles((dir, name) -> name.matches(".*\\" + ApplicationPackage.SD_NAME_SUFFIX)))); + schemaFiles.addAll(Arrays.asList(sdDir.listFiles((dir, name) -> name.matches(".*\\" + SD_NAME_SUFFIX)))); - sdDir = new File(appDir, ApplicationPackage.SCHEMAS_DIR.getRelative()); + sdDir = new File(appDir, SCHEMAS_DIR.getRelative()); if (sdDir.isDirectory()) - schemaFiles.addAll(Arrays.asList(sdDir.listFiles((dir, name) -> name.matches(".*\\" + ApplicationPackage.SD_NAME_SUFFIX)))); + schemaFiles.addAll(Arrays.asList(sdDir.listFiles((dir, name) -> name.matches(".*\\" + SD_NAME_SUFFIX)))); return schemaFiles; } @@ -445,16 +442,16 @@ public class FilesApplicationPackage implements ApplicationPackage { // Only for use by deploy processor public static List<Component> getComponents(File appDir) { List<Component> components = new ArrayList<>(); - for (Bundle bundle : Bundle.getBundles(new File(appDir, ApplicationPackage.COMPONENT_DIR))) { - components.add(new Component(bundle, new ComponentInfo(new File(ApplicationPackage.COMPONENT_DIR, bundle.getFile().getName()).getPath()))); + for (Bundle bundle : Bundle.getBundles(new File(appDir, COMPONENT_DIR))) { + components.add(new Component(bundle, new ComponentInfo(new File(COMPONENT_DIR, bundle.getFile().getName()).getPath()))); } return components; } private static List<ComponentInfo> getComponentsInfo(File appDir) { List<ComponentInfo> components = new ArrayList<>(); - for (Bundle bundle : Bundle.getBundles(new File(appDir, ApplicationPackage.COMPONENT_DIR))) { - components.add(new ComponentInfo(new File(ApplicationPackage.COMPONENT_DIR, bundle.getFile().getName()).getPath())); + for (Bundle bundle : Bundle.getBundles(new File(appDir, COMPONENT_DIR))) { + components.add(new ComponentInfo(new File(COMPONENT_DIR, bundle.getFile().getName()).getPath())); } return components; } @@ -545,10 +542,10 @@ public class FilesApplicationPackage implements ApplicationPackage { if (new File(name).isAbsolute()) throw new IllegalArgumentException("Absolute path to ranking expression file is not allowed: " + name); - File sdDir = new File(appDir, ApplicationPackage.SCHEMAS_DIR.getRelative()); + File sdDir = new File(appDir, SCHEMAS_DIR.getRelative()); File expressionFile = new File(sdDir, name); if ( ! expressionFile.exists()) { - sdDir = new File(appDir, ApplicationPackage.SEARCH_DEFINITIONS_DIR.getRelative()); + sdDir = new File(appDir, SEARCH_DEFINITIONS_DIR.getRelative()); expressionFile = new File(sdDir, name); } return expressionFile; @@ -611,7 +608,7 @@ public class FilesApplicationPackage implements ApplicationPackage { File servicesFile = validateServicesFile(); preprocessXML(new File(preprocessedDir, SERVICES), servicesFile, zone); preprocessXML(new File(preprocessedDir, HOSTS), getHostsFile(), zone); - FilesApplicationPackage preprocessed = FilesApplicationPackage.fromFile(preprocessedDir, includeSourceFiles); + FilesApplicationPackage preprocessed = fromFile(preprocessedDir, includeSourceFiles); preprocessed.copyUserDefsIntoApplication(); return preprocessed; } @@ -629,7 +626,7 @@ public class FilesApplicationPackage implements ApplicationPackage { ConfigDefinitionDir defDir = new ConfigDefinitionDir(destination); // Copy the user's def files from components. List<Bundle> bundlesAdded = new ArrayList<>(); - for (FilesApplicationPackage.Component component : FilesApplicationPackage.getComponents(appSubDirs.root())) { + for (Component component : getComponents(appSubDirs.root())) { Bundle bundle = component.getBundle(); defDir.addConfigDefinitionsFromBundle(bundle, bundlesAdded); bundlesAdded.add(bundle); @@ -645,7 +642,7 @@ public class FilesApplicationPackage implements ApplicationPackage { MessageDigest md5; try { md5 = MessageDigest.getInstance("MD5"); - for (File file : appDir.listFiles((dir, name) -> !name.equals(ApplicationPackage.EXT_DIR) && !name.startsWith("."))) { + for (File file : appDir.listFiles((dir, name) -> !name.equals(EXT_DIR) && !name.startsWith("."))) { addPathToDigest(file, "", md5, true, false); } return toLowerCase(HexDump.toHexString(md5.digest())); diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java index 6888626633b..e779d59ba24 100644 --- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java +++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/PreGeneratedFileRegistry.java @@ -7,7 +7,11 @@ import com.yahoo.config.application.api.FileRegistry; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; /** @@ -50,7 +54,7 @@ public class PreGeneratedFileRegistry implements FileRegistry { } public static String exportRegistry(FileRegistry registry) { - List<FileRegistry.Entry> entries = registry.export(); + List<Entry> entries = registry.export(); StringBuilder builder = new StringBuilder(); builder.append(registry.fileSourceHost()).append('\n'); @@ -66,7 +70,11 @@ public class PreGeneratedFileRegistry implements FileRegistry { } public FileReference addFile(String relativePath) { - return new FileReference(path2Hash.get(relativePath)); + String reference = path2Hash.get(relativePath); + if (reference == null) { + throw new IllegalArgumentException("File '" + relativePath + "' not found"); + } + return new FileReference(reference); } @Override diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java index 0d868057adb..d97ff5ca774 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationPackage.java @@ -10,10 +10,7 @@ import com.yahoo.path.Path; import com.yahoo.text.XML; import com.yahoo.vespa.config.ConfigDefinitionKey; import org.w3c.dom.Element; -import org.xml.sax.SAXException; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; import java.io.File; import java.io.IOException; import java.io.Reader; diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 5d28088d108..e3afdec85b9 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -19,6 +19,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URI; +import java.security.cert.X509Certificate; import java.util.List; import java.util.Optional; import java.util.Set; @@ -83,6 +84,7 @@ public interface ModelContext { @ModelFeatureFlag(owners = {"baldersheim"}) default boolean useBucketExecutorForLidSpaceCompact() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default boolean useBucketExecutorForBucketMove() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default boolean useBucketExecutorForPruneRemoved() { throw new UnsupportedOperationException("TODO specify default value"); } + @ModelFeatureFlag(owners = {"baldersheim"}) default boolean useExternalRankExpressions() { return false; } @ModelFeatureFlag(owners = {"geirst"}) default boolean enableFeedBlockInDistributor() { return true; } @ModelFeatureFlag(owners = {"baldersheim", "geirst", "toregge"}) default double maxDeadBytesRatio() { return 0.05; } @ModelFeatureFlag(owners = {"hmusum"}, removeAfter = "7.406") default int clusterControllerMaxHeapSizeInMb() { return 128; } @@ -91,7 +93,7 @@ public interface ModelContext { @ModelFeatureFlag(owners = {"tokle"}) default boolean tenantIamRole() { return false; } @ModelFeatureFlag(owners = {"vekterli"}) default int maxActivationInhibitedOutOfSyncGroups() { return 0; } @ModelFeatureFlag(owners = {"hmusum"}) default String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) { return ""; } - @ModelFeatureFlag(owners = {"bjorncs", "jonmv"}) default boolean enableJdiscHttp2() { return false; } + @ModelFeatureFlag(owners = {"bjorncs", "jonmv"}, removeAfter = "7.409") default boolean enableJdiscHttp2() { return true; } @ModelFeatureFlag(owners = {"tokle", "bjorncs"}) default boolean enableCustomAclMapping() { return false; } @ModelFeatureFlag(owners = {"geirst", "vekterli"}) default int numDistributorStripes() { return 0; } } @@ -135,6 +137,8 @@ public interface ModelContext { // Allow disabling mTLS for now, harden later default boolean allowDisableMtls() { return true; } + + default List<X509Certificate> operatorCertificates() { return List.of(); } } @Retention(RetentionPolicy.RUNTIME) diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java index a77dd65c608..3fd2d009412 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java @@ -432,7 +432,7 @@ public class DeployState implements ConfigDefinitionStore { RankProfileRegistry rankProfileRegistry = new RankProfileRegistry(); QueryProfiles queryProfiles = new QueryProfilesBuilder().build(applicationPackage, logger); SemanticRules semanticRules = new SemanticRuleBuilder().build(applicationPackage); - SearchDocumentModel searchDocumentModel = createSearchDocumentModel(rankProfileRegistry, logger, queryProfiles, validationParameters); + SearchDocumentModel searchDocumentModel = createSearchDocumentModel(rankProfileRegistry, queryProfiles, validationParameters); return new DeployState(applicationPackage, searchDocumentModel, rankProfileRegistry, @@ -458,16 +458,15 @@ public class DeployState implements ConfigDefinitionStore { } private SearchDocumentModel createSearchDocumentModel(RankProfileRegistry rankProfileRegistry, - DeployLogger logger, QueryProfiles queryProfiles, ValidationParameters validationParameters) { Collection<NamedReader> readers = applicationPackage.getSearchDefinitions(); Map<String, String> names = new LinkedHashMap<>(); - SearchBuilder builder = new SearchBuilder(applicationPackage, rankProfileRegistry, queryProfiles.getRegistry()); + SearchBuilder builder = new SearchBuilder(applicationPackage, logger, rankProfileRegistry, queryProfiles.getRegistry()); for (NamedReader reader : readers) { try { String readerName = reader.getName(); - String topLevelName = builder.importReader(reader, readerName, logger); + String topLevelName = builder.importReader(reader, readerName); String sdName = stripSuffix(readerName, ApplicationPackage.SD_NAME_SUFFIX); names.put(topLevelName, sdName); if ( ! sdName.equals(topLevelName)) { @@ -483,7 +482,7 @@ public class DeployState implements ConfigDefinitionStore { closeIgnoreException(reader.getReader()); } } - builder.build(! validationParameters.ignoreValidationErrors(), logger); + builder.build(! validationParameters.ignoreValidationErrors()); return SearchDocumentModel.fromBuilderAndNames(builder, names); } diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index e6bf3a835c6..66e728957f3 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -52,6 +52,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea private double feedConcurrency = 0.5; private boolean useBucketExecutorForPruneRemoved; private boolean enableFeedBlockInDistributor = true; + private boolean useExternalRankExpression = false; private int clusterControllerMaxHeapSizeInMb = 128; private int metricsProxyMaxHeapSizeInMb = 256; private int maxActivationInhibitedOutOfSyncGroups = 0; @@ -98,7 +99,12 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea @Override public String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) { return jvmOmitStackTraceInFastThrowOption; } @Override public int numDistributorStripes() { return numDistributorStripes; } @Override public boolean allowDisableMtls() { return allowDisableMtls; } + @Override public boolean useExternalRankExpressions() { return useExternalRankExpression; } + public TestProperties useExternalRankExpression(boolean value) { + useExternalRankExpression = value; + return this; + } public TestProperties setFeedConcurrency(double feedConcurrency) { this.feedConcurrency = feedConcurrency; return this; diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java index 602e0c80d4b..2784c111019 100644 --- a/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java +++ b/config-model/src/main/java/com/yahoo/config/model/producer/AbstractConfigProducer.java @@ -43,7 +43,7 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce private final String subId; private String configId = null; - private List<Service> descendantServices = new ArrayList<>(); + private final List<Service> descendantServices = new ArrayList<>(); private AbstractConfigProducer parent = null; @@ -59,8 +59,8 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce * Creates a new AbstractConfigProducer with the given parent and subId. * This constructor will add the resulting producer to the children of parent. * - * @param parent The parent of this ConfigProducer - * @param subId The fragment of the config id for the producer + * @param parent the parent of this ConfigProducer + * @param subId the fragment of the config id for the producer */ public AbstractConfigProducer(AbstractConfigProducer parent, String subId) { this(subId); @@ -69,7 +69,13 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce } } - protected final void setParent(AbstractConfigProducer parent) { + /** Removes this from the config model */ + protected void remove() { + if (parent != null) + parent.removeChild(this); + } + + protected final void setParent(AbstractConfigProducer<?> parent) { this.parent = parent; computeConfigId(); } @@ -92,7 +98,7 @@ public abstract class AbstractConfigProducer<CHILD extends AbstractConfigProduce /** * Adds a child to this config producer. * - * @param child The child config producer to add. + * @param child the child config producer to add */ protected void addChild(CHILD child) { if (child == null) { diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java index 2594c64b951..7200c3211ba 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java @@ -6,6 +6,7 @@ import com.yahoo.config.application.api.ComponentInfo; import com.yahoo.config.application.api.UnparsedConfigDefinition; import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.component.Version; +import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.InstanceName; @@ -116,6 +117,7 @@ public class MockApplicationPackage implements ApplicationPackage { public List<NamedReader> getSearchDefinitions() { ArrayList<NamedReader> readers = new ArrayList<>(); SearchBuilder searchBuilder = new SearchBuilder(this, + new BaseDeployLogger(), new RankProfileRegistry(), queryProfileRegistry); for (String sd : schemas) { diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java b/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java new file mode 100644 index 00000000000..77ce2dd41b5 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/DistributableResource.java @@ -0,0 +1,72 @@ +package com.yahoo.searchdefinition; + +import com.yahoo.config.FileReference; +import com.yahoo.path.Path; +import com.yahoo.vespa.model.AbstractService; +import com.yahoo.vespa.model.utils.FileSender; + +import java.util.Collection; +import java.util.Objects; + +public class DistributableResource { + public enum PathType { FILE, URI }; + + /** The search definition-unique name of this constant */ + private final String name; + private String path = null; + private String fileReference = ""; + private PathType pathType = PathType.FILE; + + public PathType getPathType() { + return pathType; + } + + public DistributableResource(String name) { + this(name, null); + } + public DistributableResource(String name, String path) { + this.name = name; + this.path = path; + } + + public void setFileName(String fileName) { + Objects.requireNonNull(fileName, "Filename cannot be null"); + this.path = fileName; + this.pathType = PathType.FILE; + } + + public void setUri(String uri) { + Objects.requireNonNull(uri, "uri cannot be null"); + this.path = uri; + this.pathType = PathType.URI; + } + + protected void setFileReference(String fileReference) { this.fileReference = fileReference; } + /** Initiate sending of this constant to some services over file distribution */ + public void sendTo(Collection<? extends AbstractService> services) { + FileReference reference = (pathType == PathType.FILE) + ? FileSender.sendFileToServices(path, services) + : FileSender.sendUriToServices(path, services); + this.fileReference = reference.value(); + } + + public String getName() { return name; } + public String getFileName() { return path; } + public Path getFilePath() { return Path.fromString(path); } + public String getUri() { return path; } + public String getFileReference() { return fileReference; } + + public void validate() { + if (path == null || path.isEmpty()) + throw new IllegalArgumentException("Distributable resource must have a file or uri."); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("resource '").append(name) + .append(pathType == PathType.FILE ? "' from file '" : " from uri ").append(path) + .append("' with ref '").append(fileReference) + .append("'"); + return b.toString(); + } +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentOnlySearch.java b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentOnlySearch.java index 5940f908be8..6e17bd0f0bb 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentOnlySearch.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentOnlySearch.java @@ -1,6 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition; +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.application.api.DeployLogger; import com.yahoo.searchdefinition.document.SDDocumentType; /** @@ -11,8 +13,8 @@ import com.yahoo.searchdefinition.document.SDDocumentType; */ public class DocumentOnlySearch extends Search { - public DocumentOnlySearch() { - // empty + public DocumentOnlySearch(ApplicationPackage applicationPackage, DeployLogger deployLogger) { + super(applicationPackage, deployLogger); } @Override diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/ImmutableSearch.java b/config-model/src/main/java/com/yahoo/searchdefinition/ImmutableSearch.java index 6ac73ad45a9..efd9ed5e88f 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/ImmutableSearch.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/ImmutableSearch.java @@ -28,6 +28,7 @@ public interface ImmutableSearch { Reader getRankingExpression(String fileName); ApplicationPackage applicationPackage(); RankingConstants rankingConstants(); + RankExpressionFiles rankExpressionFiles(); OnnxModels onnxModels(); Stream<ImmutableSDField> allImportedFields(); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/OnnxModel.java b/config-model/src/main/java/com/yahoo/searchdefinition/OnnxModel.java index 3e5726d6d94..3c42987512b 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/OnnxModel.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/OnnxModel.java @@ -1,14 +1,9 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition; -import com.yahoo.config.FileReference; -import com.yahoo.path.Path; import com.yahoo.tensor.TensorType; -import com.yahoo.vespa.model.AbstractService; import com.yahoo.vespa.model.ml.OnnxModelInfo; -import com.yahoo.vespa.model.utils.FileSender; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -19,42 +14,26 @@ import java.util.Objects; * * @author lesters */ -public class OnnxModel { +public class OnnxModel extends DistributableResource { - public enum PathType {FILE, URI}; - - private final String name; - private PathType pathType = PathType.FILE; - private String path = null; - private String fileReference = ""; private OnnxModelInfo modelInfo = null; private Map<String, String> inputMap = new HashMap<>(); private Map<String, String> outputMap = new HashMap<>(); public OnnxModel(String name) { - this.name = name; + super(name); } public OnnxModel(String name, String fileName) { - this(name); - this.path = fileName; + super(name, fileName); validate(); } - public void setFileName(String fileName) { - Objects.requireNonNull(fileName, "Filename cannot be null"); - this.path = fileName; - this.pathType = PathType.FILE; - } - + @Override public void setUri(String uri) { throw new IllegalArgumentException("URI for ONNX models are not currently supported"); } - public PathType getPathType() { - return pathType; - } - public void addInputNameMapping(String onnxName, String vespaName) { addInputNameMapping(onnxName, vespaName, true); } @@ -90,20 +69,6 @@ public class OnnxModel { this.modelInfo = modelInfo; } - /** Initiate sending of this constant to some services over file distribution */ - public void sendTo(Collection<? extends AbstractService> services) { - FileReference reference = (pathType == OnnxModel.PathType.FILE) - ? FileSender.sendFileToServices(path, services) - : FileSender.sendUriToServices(path, services); - this.fileReference = reference.value(); - } - - public String getName() { return name; } - public String getFileName() { return path; } - public Path getFilePath() { return Path.fromString(path); } - public String getUri() { return path; } - public String getFileReference() { return fileReference; } - public Map<String, String> getInputMap() { return Collections.unmodifiableMap(inputMap); } public Map<String, String> getOutputMap() { return Collections.unmodifiableMap(outputMap); } @@ -114,19 +79,4 @@ public class OnnxModel { TensorType getTensorType(String onnxName, Map<String, TensorType> inputTypes) { return modelInfo != null ? modelInfo.getTensorType(onnxName, inputTypes) : TensorType.empty; } - - public void validate() { - if (path == null || path.isEmpty()) - throw new IllegalArgumentException("ONNX models must have a file or uri."); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("onnx-model '").append(name) - .append(pathType == PathType.FILE ? "' from file '" : " from uri ").append(path) - .append("' with ref '").append(fileReference) - .append("'"); - return b.toString(); - } - } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankExpressionFile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankExpressionFile.java new file mode 100644 index 00000000000..56385efeb0b --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankExpressionFile.java @@ -0,0 +1,35 @@ +package com.yahoo.searchdefinition; + +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.vespa.model.AbstractService; +import com.yahoo.vespa.model.utils.FileSender; + +import java.util.Collection; + +public class RankExpressionFile extends DistributableResource { + + public RankExpressionFile(String name, String path) { + super(name, path); + validate(); + } + + @Override + public void sendTo(Collection<? extends AbstractService> services) { + /* + * TODO This is a very dirty hack due to using both SEARCH_DEFINITIONS_DIR and SCHEMA_DIR + * and doing so inconsistently, combined with using both fields from application package on disk and in zookeeper. + * The mess is spread out nicely, but ZookeeperClient, and writeSearchDefinitions and ZkApplicationPackage and FilesApplicationPackage + * should be consolidated + */ + try { + setFileReference(FileSender.sendFileToServices(ApplicationPackage.SCHEMAS_DIR + "/" + getFileName(), services).value()); + } catch (IllegalArgumentException e1) { + try { + setFileReference(FileSender.sendFileToServices(ApplicationPackage.SEARCH_DEFINITIONS_DIR + "/" + getFileName(), services).value()); + } catch (IllegalArgumentException e2) { + throw new IllegalArgumentException("Failed to find expression file '" + getFileName() + "' in '" + + ApplicationPackage.SEARCH_DEFINITIONS_DIR + "' or '" + ApplicationPackage.SCHEMAS_DIR + "'.", e2); + } + } + } +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankExpressionFiles.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankExpressionFiles.java new file mode 100644 index 00000000000..ebc91e0693f --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankExpressionFiles.java @@ -0,0 +1,35 @@ +package com.yahoo.searchdefinition; + +import com.yahoo.vespa.model.AbstractService; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class RankExpressionFiles { + private final Map<String, RankExpressionFile> expressions = new HashMap<>(); + + public void add(RankExpressionFile expression) { + expression.validate(); + String name = expression.getName(); + if (expressions.containsKey(name)) + throw new IllegalArgumentException("Rank expression file '" + name + "' defined twice"); + expressions.put(name, expression); + } + + /** Returns the ranking constant with the given name, or null if not present */ + public RankExpressionFile get(String name) { + return expressions.get(name); + } + + /** Returns a read-only map of the ranking constants in this indexed by name */ + public Map<String, RankExpressionFile> asMap() { + return Collections.unmodifiableMap(expressions); + } + + /** Initiate sending of these constants to some services over file distribution */ + public void sendTo(Collection<? extends AbstractService> services) { + expressions.values().forEach(constant -> constant.sendTo(services)); + } +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index 95b291cf744..b0a7d2aaca2 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -52,6 +52,8 @@ import java.util.stream.Stream; */ public class RankProfile implements Cloneable { + public final static String FIRST_PHASE = "firstphase"; + public final static String SECOND_PHASE = "secondphase"; /** The search definition-unique name of this rank profile */ private final String name; @@ -76,6 +78,8 @@ public class RankProfile implements Cloneable { /** The ranking expression to be used for second phase */ private RankingExpression secondPhaseRanking = null; + private Set<String> externalFileExpressions = new HashSet<>(); + /** Number of hits to be reranked in second phase, -1 means use default */ private int rerankCount = -1; @@ -165,6 +169,10 @@ public class RankProfile implements Cloneable { return search != null ? search.rankingConstants() : model.rankingConstants(); } + public RankExpressionFiles rankExpressionFiles() { + return search != null ? search.rankExpressionFiles() : model.rankExpressionFiles(); + } + public Map<String, OnnxModel> onnxModels() { return search != null ? search.onnxModels().asMap() : onnxModels.asMap(); } @@ -316,7 +324,7 @@ public class RankProfile implements Cloneable { public void addConstant(String name, Value value) { if (value instanceof TensorValue) { - TensorType type = ((TensorValue)value).type(); + TensorType type = value.type(); if (type.dimensions().stream().anyMatch(d -> d.isIndexed() && d.size().isEmpty())) throw new IllegalArgumentException("Illegal type of constant " + name + " type " + type + ": Dense tensor dimensions must have a size"); @@ -370,9 +378,44 @@ public class RankProfile implements Cloneable { this.firstPhaseRanking = rankingExpression; } + public String getUniqueExpressionName(String name) { + return getName() + "_" + name; + } + public String getFirstPhaseFile() { + String name = FIRST_PHASE; + if (externalFileExpressions.contains(name)) { + return rankExpressionFiles().get(getUniqueExpressionName(name)).getFileName(); + } + if ((firstPhaseRanking == null) && (getInherited() != null)) { + return getInherited().getFirstPhaseFile(); + } + return null; + } + + public String getSecondPhaseFile() { + String name = SECOND_PHASE; + if (externalFileExpressions.contains(name)) { + return rankExpressionFiles().get(getUniqueExpressionName(name)).getFileName(); + } + if ((secondPhaseRanking == null) && (getInherited() != null)) { + return getInherited().getSecondPhaseFile(); + } + return null; + } + + public String getExpressionFile(String name) { + if (externalFileExpressions.contains(name)) { + return rankExpressionFiles().get(getUniqueExpressionName(name)).getFileName(); + } + if (getInherited() != null) { + return getInherited().getExpressionFile(name); + } + return null; + } + public void setFirstPhaseRanking(String expression) { try { - this.firstPhaseRanking = parseRankingExpression("firstphase", expression); + this.firstPhaseRanking = parseRankingExpression(FIRST_PHASE, expression); } catch (ParseException e) { throw new IllegalArgumentException("Illegal first phase ranking function", e); @@ -389,13 +432,9 @@ public class RankProfile implements Cloneable { return null; } - public void setSecondPhaseRanking(RankingExpression rankingExpression) { - this.secondPhaseRanking = rankingExpression; - } - public void setSecondPhaseRanking(String expression) { try { - this.secondPhaseRanking = parseRankingExpression("secondphase", expression); + this.secondPhaseRanking = parseRankingExpression(SECOND_PHASE, expression); } catch (ParseException e) { throw new IllegalArgumentException("Illegal second phase ranking function", e); @@ -436,7 +475,6 @@ public class RankProfile implements Cloneable { * the final (with inheritance included) summary features of the given parent. * The profile must be the profile which is directly inherited by this. * - * @param parentProfile */ public void setInheritedSummaryFeatures(String parentProfile) { if ( ! parentProfile.equals(inheritedName)) @@ -496,12 +534,7 @@ public class RankProfile implements Cloneable { private void addRankProperty(RankProperty rankProperty) { // Just the usual multimap semantics here - List<RankProperty> properties = rankProperties.get(rankProperty.getName()); - if (properties == null) { - properties = new ArrayList<>(1); - rankProperties.put(rankProperty.getName(), properties); - } - properties.add(rankProperty); + rankProperties.computeIfAbsent(rankProperty.getName(), (String key) -> new ArrayList<>(1)).add(rankProperty); } @Override @@ -675,18 +708,27 @@ public class RankProfile implements Cloneable { } } - private Reader openRankingExpressionReader(String expName, String expression) { - if ( ! expression.startsWith("file:")) return new StringReader(expression); - + private static String extractFileName(String expression) { String fileName = expression.substring("file:".length()).trim(); if ( ! fileName.endsWith(ApplicationPackage.RANKEXPRESSION_NAME_SUFFIX)) fileName = fileName + ApplicationPackage.RANKEXPRESSION_NAME_SUFFIX; + return fileName; + } + + private Reader openRankingExpressionReader(String expName, String expression) { + if ( ! expression.startsWith("file:")) return new StringReader(expression); + + String fileName = extractFileName(expression); File file = new File(fileName); - if ( ! (file.isAbsolute()) && file.getPath().contains("/")) // See ticket 4102122 + if ( ! file.isAbsolute() && file.getPath().contains("/")) // See ticket 4102122 throw new IllegalArgumentException("In " + getName() +", " + expName + ", ranking references file '" + file + "' in subdirectory, which is not supported."); + /* TODO balder: Disabled until end-2-end verified + rankExpressionFiles().add(new RankExpressionFile(getUniqueExpressionName(expName), fileName)); + externalFileExpressions.add(expName); + */ return search.getRankingExpression(fileName); } @@ -918,12 +960,12 @@ public class RankProfile implements Cloneable { */ public static class RankSetting implements Serializable { - private String fieldName; + private final String fieldName; - private Type type; + private final Type type; /** The rank value */ - private Object value; + private final Object value; public enum Type { @@ -932,10 +974,10 @@ public class RankProfile implements Cloneable { WEIGHT("weight"), PREFERBITVECTOR("preferbitvector",true); - private String name; + private final String name; /** True if this setting really pertains to an index, not a field within an index */ - private boolean isIndexLevel; + private final boolean isIndexLevel; Type(String name) { this(name,false); @@ -1008,8 +1050,8 @@ public class RankProfile implements Cloneable { /** A rank property. Rank properties are Value Objects */ public static class RankProperty implements Serializable { - private String name; - private String value; + private final String name; + private final String value; public RankProperty(String name, String value) { this.name = name; @@ -1084,7 +1126,6 @@ public class RankProfile implements Cloneable { public void setMinGroups(int value) { minGroups = value; } public void setCutoffFactor(double value) { cutoffFactor = value; } public void setCutoffStrategy(Diversity.CutoffStrategy strategy) { cutoffStrategy = strategy; } - public void setCutoffStrategy(String strategy) { cutoffStrategy = Diversity.CutoffStrategy.valueOf(strategy); } public String getAttribute() { return attribute; } public int getMinGroups() { return minGroups; } public double getCutoffFactor() { return cutoffFactor; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstant.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstant.java index b41cf582204..8e376f13615 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstant.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstant.java @@ -1,13 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition; -import com.yahoo.config.FileReference; import com.yahoo.tensor.TensorType; -import com.yahoo.vespa.model.AbstractService; -import com.yahoo.vespa.model.utils.FileSender; - -import java.util.Collection; -import java.util.Objects; /** * A global ranking constant distributed using file distribution. @@ -17,81 +11,38 @@ import java.util.Objects; * @author arnej * @author bratseth */ -public class RankingConstant { - - public enum PathType {FILE, URI}; - - /** The search definition-unique name of this constant */ - private final String name; +public class RankingConstant extends DistributableResource { private TensorType tensorType = null; - private String path = null; - private String fileReference = ""; - - public PathType getPathType() { - return pathType; - } - - private PathType pathType = PathType.FILE; public RankingConstant(String name) { - this.name = name; + super(name); } public RankingConstant(String name, TensorType type, String fileName) { - this(name); + super(name, fileName); this.tensorType = type; - this.path = fileName; validate(); } - public void setFileName(String fileName) { - Objects.requireNonNull(fileName, "Filename cannot be null"); - this.path = fileName; - this.pathType = PathType.FILE; - } - - public void setUri(String uri) { - Objects.requireNonNull(uri, "uri cannot be null"); - this.path = uri; - this.pathType = PathType.URI; - } - public void setType(TensorType type) { this.tensorType = type; } - /** Initiate sending of this constant to some services over file distribution */ - public void sendTo(Collection<? extends AbstractService> services) { - FileReference reference = (pathType == RankingConstant.PathType.FILE) - ? FileSender.sendFileToServices(path, services) - : FileSender.sendUriToServices(path, services); - this.fileReference = reference.value(); - } - - public String getName() { return name; } - public String getFileName() { return path; } - public String getUri() { return path; } - public String getFileReference() { return fileReference; } public TensorType getTensorType() { return tensorType; } public String getType() { return tensorType.toString(); } public void validate() { - if (path == null || path.isEmpty()) - throw new IllegalArgumentException("Ranking constants must have a file or uri."); + super.validate(); if (tensorType == null) - throw new IllegalArgumentException("Ranking constant '" + name + "' must have a type."); + throw new IllegalArgumentException("Ranking constant '" + getName() + "' must have a type."); if (tensorType.dimensions().stream().anyMatch(d -> d.isIndexed() && d.size().isEmpty())) - throw new IllegalArgumentException("Illegal type in field " + name + " type " + tensorType + + throw new IllegalArgumentException("Illegal type in field " + getName() + " type " + tensorType + ": Dense tensor dimensions must have a size"); } public String toString() { - StringBuilder b = new StringBuilder(); - b.append("constant '").append(name) - .append(pathType == PathType.FILE ? "' from file '" : " from uri ").append(path) - .append("' with ref '").append(fileReference) - .append("' of type '").append(tensorType) - .append("'"); + StringBuilder b = new StringBuilder(super.toString()) + .append("' of type '").append(tensorType).append("'"); return b.toString(); } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstants.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstants.java index adefa5566ab..82381aa63fc 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstants.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankingConstants.java @@ -40,5 +40,4 @@ public class RankingConstants { public void sendTo(Collection<? extends AbstractService> services) { constants.values().forEach(constant -> constant.sendTo(services)); } - } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java index 9b7434dccab..b07740620f8 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java @@ -2,6 +2,8 @@ package com.yahoo.searchdefinition; import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.document.Field; import com.yahoo.searchdefinition.derived.SummaryClass; import com.yahoo.searchdefinition.document.Attribute; @@ -15,6 +17,7 @@ import com.yahoo.searchdefinition.document.TemporaryImportedFields; import com.yahoo.searchdefinition.document.annotation.SDAnnotationType; import com.yahoo.vespa.documentmodel.DocumentSummary; import com.yahoo.vespa.documentmodel.SummaryField; +import com.yahoo.vespa.model.AbstractService; import java.io.Reader; import java.util.ArrayList; @@ -27,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; @@ -42,7 +46,6 @@ import java.util.stream.Stream; // Ensure that after the processing step, all implicit instances of the above types are explicitly represented public class Search implements ImmutableSearch { - private static final Logger log = Logger.getLogger(Search.class.getName()); private static final String SD_DOC_FIELD_NAME = "sddocname"; private static final List<String> RESERVED_NAMES = Arrays.asList( "index", "index_url", "summary", "attribute", "select_input", "host", SummaryClass.DOCUMENT_ID_FIELD, @@ -59,7 +62,7 @@ public class Search implements ImmutableSearch { private String name; /** True if this doesn't define a search, just a document type */ - private boolean documentsOnly = false; + private final boolean documentsOnly; private boolean rawAsBase64 = false; @@ -78,6 +81,9 @@ public class Search implements ImmutableSearch { /** The explicitly defined summaries of this search definition. _Must_ preserve order. */ private final Map<String, DocumentSummary> summaries = new LinkedHashMap<>(); + /** External rank expression files of this */ + private final RankExpressionFiles rankExpressionFiles = new RankExpressionFiles(); + /** Ranking constants of this */ private final RankingConstants rankingConstants = new RankingConstants(); @@ -88,26 +94,33 @@ public class Search implements ImmutableSearch { private Optional<ImportedFields> importedFields = Optional.empty(); private final ApplicationPackage applicationPackage; + private final DeployLogger deployLogger; - /** - * Creates a search definition which just holds a set of documents which should not (here, directly) be searchable - */ - protected Search() { - applicationPackage = null; - documentsOnly = true; + /** Testin only */ + public Search(String name) { + this(name, null, new BaseDeployLogger()); } - /** * Creates a proper search definition * * @param name of the the searchdefinition * @param applicationPackage the application containing this */ - public Search(String name, ApplicationPackage applicationPackage) { - this.applicationPackage = applicationPackage; + public Search(String name, ApplicationPackage applicationPackage, DeployLogger deployLogger) { + this(applicationPackage, deployLogger, false); this.name = name; } + protected Search(ApplicationPackage applicationPackage, DeployLogger deployLogger) { + this(applicationPackage, deployLogger, true); + } + + private Search(ApplicationPackage applicationPackage, DeployLogger deployLogger, boolean documentsOnly) { + this.applicationPackage = applicationPackage; + this.deployLogger = deployLogger; + this.documentsOnly = documentsOnly; + } + protected void setName(String name) { this.name = name; } @@ -172,11 +185,20 @@ public class Search implements ImmutableSearch { } @Override + public RankExpressionFiles rankExpressionFiles() { return rankExpressionFiles; } + + @Override public RankingConstants rankingConstants() { return rankingConstants; } @Override public OnnxModels onnxModels() { return onnxModels; } + public void sendTo(Collection<? extends AbstractService> services) { + rankingConstants.sendTo(services); + rankExpressionFiles.sendTo(services); + onnxModels.sendTo(services); + } + public Optional<TemporaryImportedFields> temporaryImportedFields() { return temporaryImportedFields; } @@ -310,7 +332,7 @@ public class Search implements ImmutableSearch { */ public void addExtraField(SDField field) { if (fields.containsKey(field.getName())) { - log.warning("Duplicate field " + field.getName() + " in search definition " + getName()); + deployLogger.logApplicationPackage(Level.WARNING, "Duplicate field " + field.getName() + " in search definition " + getName()); } else { field.setIsExtraField(true); fields.put(field.getName(), field); @@ -422,7 +444,7 @@ public class Search implements ImmutableSearch { if (current.getRankType() != null && !consolidated.getRankType().equals(current.getRankType())) { - log.warning("Conflicting rank type settings for " + + deployLogger.logApplicationPackage(Level.WARNING, "Conflicting rank type settings for " + first.getName() + " in " + this + ", using " + consolidated.getRankType()); } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java index 852fdc17726..fc110504f4d 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java @@ -41,53 +41,72 @@ import java.util.List; public class SearchBuilder { private final DocumentTypeManager docTypeMgr = new DocumentTypeManager(); - private List<Search> searchList = new LinkedList<>(); - private ApplicationPackage app; - private boolean isBuilt = false; - private DocumentModel model = new DocumentModel(); + private final DocumentModel model = new DocumentModel(); + private final ApplicationPackage app; private final RankProfileRegistry rankProfileRegistry; private final QueryProfileRegistry queryProfileRegistry; + private final DeployLogger deployLogger; + private List<Search> searchList = new LinkedList<>(); + private boolean isBuilt = false; /** True to build the document aspect only, skipping instantiation of rank profiles */ private final boolean documentsOnly; /** For testing only */ public SearchBuilder() { - this(MockApplicationPackage.createEmpty(), new RankProfileRegistry(), new QueryProfileRegistry()); + this(new RankProfileRegistry(), new QueryProfileRegistry()); + } + + /** For testing only */ + public SearchBuilder(DeployLogger deployLogger) { + this(MockApplicationPackage.createEmpty(), deployLogger); + } + + /** For testing only */ + public SearchBuilder(DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry) { + this(MockApplicationPackage.createEmpty(), deployLogger, rankProfileRegistry); } /** Used for generating documents for typed access to document fields in Java */ public SearchBuilder(boolean documentsOnly) { - this(MockApplicationPackage.createEmpty(), new RankProfileRegistry(), new QueryProfileRegistry(), documentsOnly); + this(MockApplicationPackage.createEmpty(), new BaseDeployLogger(), new RankProfileRegistry(), new QueryProfileRegistry(), documentsOnly); } /** For testing only */ - public SearchBuilder(ApplicationPackage app) { - this(app, new RankProfileRegistry(), new QueryProfileRegistry()); + public SearchBuilder(ApplicationPackage app, DeployLogger deployLogger) { + this(app, deployLogger, new RankProfileRegistry(), new QueryProfileRegistry()); + } + + /** For testing only */ + public SearchBuilder(ApplicationPackage app, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry) { + this(app, deployLogger, rankProfileRegistry, new QueryProfileRegistry()); } /** For testing only */ public SearchBuilder(RankProfileRegistry rankProfileRegistry) { - this(MockApplicationPackage.createEmpty(), rankProfileRegistry, new QueryProfileRegistry()); + this(rankProfileRegistry, new QueryProfileRegistry()); } /** For testing only */ public SearchBuilder(RankProfileRegistry rankProfileRegistry, QueryProfileRegistry queryProfileRegistry) { - this(MockApplicationPackage.createEmpty(), rankProfileRegistry, queryProfileRegistry); + this(MockApplicationPackage.createEmpty(), new BaseDeployLogger(), rankProfileRegistry, queryProfileRegistry); } public SearchBuilder(ApplicationPackage app, + DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfileRegistry queryProfileRegistry) { - this(app, rankProfileRegistry, queryProfileRegistry, false); + this(app, deployLogger, rankProfileRegistry, queryProfileRegistry, false); } - public SearchBuilder(ApplicationPackage app, - RankProfileRegistry rankProfileRegistry, - QueryProfileRegistry queryProfileRegistry, - boolean documentsOnly) { + private SearchBuilder(ApplicationPackage app, + DeployLogger deployLogger, + RankProfileRegistry rankProfileRegistry, + QueryProfileRegistry queryProfileRegistry, + boolean documentsOnly) { this.app = app; this.rankProfileRegistry = rankProfileRegistry; this.queryProfileRegistry = queryProfileRegistry; + this.deployLogger = deployLogger; this.documentsOnly = documentsOnly; } @@ -95,29 +114,17 @@ public class SearchBuilder { * Import search definition. * * @param fileName The name of the file to import. - * @param deployLogger Logger for deploy messages. * @return The name of the imported object. * @throws IOException Thrown if the file can not be read for some reason. * @throws ParseException Thrown if the file does not contain a valid search definition. ``` */ - public String importFile(String fileName, DeployLogger deployLogger) throws IOException, ParseException { + public String importFile(String fileName) throws IOException, ParseException { File file = new File(fileName); - return importString(IOUtils.readFile(file), file.getAbsoluteFile().getParent(), deployLogger); + return importString(IOUtils.readFile(file), file.getAbsoluteFile().getParent()); } - /** - * Import search definition. - * - * @param fileName The name of the file to import. - * @return The name of the imported object. - * @throws IOException Thrown if the file can not be read for some reason. - * @throws ParseException Thrown if the file does not contain a valid search definition. - */ - public String importFile(String fileName) throws IOException, ParseException { - return importFile(fileName, new BaseDeployLogger()); - } - public String importFile(Path file) throws IOException, ParseException { - return importFile(file.toString(), new BaseDeployLogger()); + private String importFile(Path file) throws IOException, ParseException { + return importFile(file.toString()); } /** @@ -129,17 +136,8 @@ public class SearchBuilder { * @return The name of the imported object. * @throws ParseException Thrown if the file does not contain a valid search definition. */ - public String importReader(NamedReader reader, String searchDefDir, DeployLogger deployLogger) throws IOException, ParseException { - return importString(IOUtils.readAll(reader), searchDefDir, deployLogger); - } - - /** - * See #{@link #importReader} - * - * Convenience, should only be used for testing as logs will be swallowed. - */ public String importReader(NamedReader reader, String searchDefDir) throws IOException, ParseException { - return importString(IOUtils.readAll(reader), searchDefDir, new BaseDeployLogger()); + return importString(IOUtils.readAll(reader), searchDefDir); } /** @@ -150,21 +148,10 @@ public class SearchBuilder { * @throws ParseException thrown if the file does not contain a valid search definition. */ public String importString(String str) throws ParseException { - return importString(str, null, new BaseDeployLogger()); - } - - /** - * Import search definition. - * - * @param str the string to parse. - * @return the name of the imported object. - * @throws ParseException thrown if the file does not contain a valid search definition. - */ - public String importString(String str, DeployLogger logger) throws ParseException { - return importString(str, null, logger); + return importString(str, null); } - private String importString(String str, String searchDefDir, DeployLogger deployLogger) throws ParseException { + private String importString(String str, String searchDefDir) throws ParseException { SimpleCharStream stream = new SimpleCharStream(str); try { return importRawSearch(new SDParser(stream, deployLogger, app, rankProfileRegistry, documentsOnly) @@ -208,7 +195,7 @@ public class SearchBuilder { * @throws IllegalStateException Thrown if this method has already been called. */ public void build() { - build(true, new BaseDeployLogger()); + build(true); } /** @@ -216,9 +203,8 @@ public class SearchBuilder { * #getSearch(String)} method. * * @throws IllegalStateException Thrown if this method has already been called. - * @param deployLogger The logger to use during build */ - public void build(boolean validate, DeployLogger deployLogger) { + public void build(boolean validate) { if (isBuilt) throw new IllegalStateException("Model already built"); List<Search> built = new ArrayList<>(); @@ -325,9 +311,9 @@ public class SearchBuilder { } public static SearchBuilder createFromString(String sd, DeployLogger logger) throws ParseException { - SearchBuilder builder = new SearchBuilder(MockApplicationPackage.createEmpty()); - builder.importString(sd, logger); - builder.build(true, logger); + SearchBuilder builder = new SearchBuilder(logger); + builder.importString(sd); + builder.build(true); return builder; } @@ -386,12 +372,13 @@ public class SearchBuilder { QueryProfileRegistry queryprofileRegistry) throws IOException, ParseException { SearchBuilder builder = new SearchBuilder(MockApplicationPackage.createEmpty(), + deployLogger, rankProfileRegistry, queryprofileRegistry); for (String fileName : fileNames) { builder.importFile(fileName); } - builder.build(true, deployLogger); + builder.build(true); return builder; } @@ -413,15 +400,16 @@ public class SearchBuilder { public static SearchBuilder createFromDirectory(String dir, RankProfileRegistry rankProfileRegistry, QueryProfileRegistry queryProfileRegistry, - DeployLogger logger, + DeployLogger deployLogger, ApplicationPackage applicationPackage) throws IOException, ParseException { SearchBuilder builder = new SearchBuilder(applicationPackage, + deployLogger, rankProfileRegistry, queryProfileRegistry); for (Iterator<Path> i = Files.list(new File(dir).toPath()).filter(p -> p.getFileName().toString().endsWith(".sd")).iterator(); i.hasNext(); ) { builder.importFile(i.next()); } - builder.build(true, logger); + builder.build(true); return builder; } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java index 4d6ce783947..52d99c523ea 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java @@ -85,7 +85,8 @@ public class DerivedConfiguration { summaries = new Summaries(search, deployLogger); summaryMap = new SummaryMap(search); juniperrc = new Juniperrc(search); - rankProfileList = new RankProfileList(search, search.rankingConstants(), attributeFields, rankProfileRegistry, queryProfiles, importedModels, deployProperties); + rankProfileList = new RankProfileList(search, search.rankingConstants(), search.rankExpressionFiles(), attributeFields, + rankProfileRegistry, queryProfiles, importedModels, deployProperties); indexingScript = new IndexingScript(search); indexInfo = new IndexInfo(search); indexSchema = new IndexSchema(search); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java index 42fa1df802b..d414b9ed79f 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java @@ -6,6 +6,8 @@ import com.yahoo.config.model.api.ModelContext; import com.yahoo.search.query.profile.QueryProfileRegistry; import com.yahoo.searchdefinition.OnnxModel; import com.yahoo.searchdefinition.OnnxModels; +import com.yahoo.searchdefinition.RankExpressionFile; +import com.yahoo.searchdefinition.RankExpressionFiles; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.RankingConstant; import com.yahoo.searchdefinition.RankingConstants; @@ -14,6 +16,7 @@ import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.Search; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.model.AbstractService; import java.util.Collection; @@ -25,21 +28,21 @@ import java.util.logging.Logger; * * @author bratseth */ -public class RankProfileList extends Derived implements RankProfilesConfig.Producer, - RankingConstantsConfig.Producer, - OnnxModelsConfig.Producer { +public class RankProfileList extends Derived implements RankProfilesConfig.Producer { private static final Logger log = Logger.getLogger(RankProfileList.class.getName()); private final Map<String, RawRankProfile> rankProfiles = new java.util.LinkedHashMap<>(); private final RankingConstants rankingConstants; + private final RankExpressionFiles rankExpressionFiles; private final OnnxModels onnxModels; public static RankProfileList empty = new RankProfileList(); private RankProfileList() { - this.rankingConstants = new RankingConstants(); - this.onnxModels = new OnnxModels(); + rankingConstants = new RankingConstants(); + rankExpressionFiles = new RankExpressionFiles(); + onnxModels = new OnnxModels(); } /** @@ -50,6 +53,7 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ */ public RankProfileList(Search search, RankingConstants rankingConstants, + RankExpressionFiles rankExpressionFiles, AttributeFields attributeFields, RankProfileRegistry rankProfileRegistry, QueryProfileRegistry queryProfiles, @@ -57,7 +61,8 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ ModelContext.Properties deployProperties) { setName(search == null ? "default" : search.getName()); this.rankingConstants = rankingConstants; - this.onnxModels = search == null ? new OnnxModels() : search.onnxModels(); // as ONNX models come from parsing rank expressions + this.rankExpressionFiles = rankExpressionFiles; + onnxModels = search == null ? new OnnxModels() : search.onnxModels(); // as ONNX models come from parsing rank expressions deriveRankProfiles(rankProfileRegistry, queryProfiles, importedModels, search, attributeFields, deployProperties); } @@ -93,11 +98,9 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ return rankProfiles.get(name); } - public void sendConstantsTo(Collection<? extends AbstractService> services) { + public void sendTo(Collection<? extends AbstractService> services) { rankingConstants.sendTo(services); - } - - public void sendOnnxModelsTo(Collection<? extends AbstractService> services) { + rankExpressionFiles.sendTo(services); onnxModels.sendTo(services); } @@ -111,7 +114,10 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ } } - @Override + public void getConfig(RankingExpressionsConfig.Builder builder) { + rankExpressionFiles.asMap().values().forEach((expr) -> builder.expression.add(new RankingExpressionsConfig.Expression.Builder().name(expr.getName()).fileref(expr.getFileReference()))); + } + public void getConfig(RankingConstantsConfig.Builder builder) { for (RankingConstant constant : rankingConstants.asMap().values()) { if ("".equals(constant.getFileReference())) @@ -124,7 +130,6 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ } } - @Override public void getConfig(OnnxModelsConfig.Builder builder) { for (OnnxModel model : onnxModels.asMap().values()) { if ("".equals(model.getFileReference())) diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java index c656a426b61..41ac1e17d93 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java @@ -125,8 +125,8 @@ public class RawRankProfile implements RankProfilesConfig.Producer { */ private Map<String, FieldRankSettings> fieldRankSettings = new java.util.LinkedHashMap<>(); + private final RankProfile rankProfile; private RankingExpression firstPhaseRanking = null; - private RankingExpression secondPhaseRanking = null; private Set<ReferenceNode> summaryFeatures = new LinkedHashSet<>(); @@ -159,6 +159,7 @@ public class RawRankProfile implements RankProfilesConfig.Producer { private final Map<String, String> attributeTypes; private final Map<String, String> queryFeatureTypes; + private final boolean useExternalExpressionFiles; private Set<String> filterFields = new java.util.LinkedHashSet<>(); @@ -166,11 +167,13 @@ public class RawRankProfile implements RankProfilesConfig.Producer { * Creates a raw rank profile from the given rank profile */ Deriver(RankProfile rankProfile, QueryProfileRegistry queryProfiles, ImportedMlModels importedModels, - AttributeFields attributeFields, ModelContext.Properties deployProperties) + AttributeFields attributeFields, ModelContext.Properties deployProperties) { + this.rankProfile = rankProfile; RankProfile compiled = rankProfile.compile(queryProfiles, importedModels); attributeTypes = compiled.getAttributeTypes(); queryFeatureTypes = compiled.getQueryFeatureTypes(); + useExternalExpressionFiles = deployProperties.featureFlags().useExternalRankExpressions(); deriveRankingFeatures(compiled, deployProperties); deriveRankTypeSetting(compiled, attributeFields); deriveFilterFields(compiled); @@ -230,13 +233,13 @@ public class RawRankProfile implements RankProfilesConfig.Producer { Map<String, String> functionProperties) { SerializationContext context = new SerializationContext(functionExpressions, null, functionProperties); for (Map.Entry<String, RankProfile.RankingExpressionFunction> e : functions.entrySet()) { + if (useExternalExpressionFiles && rankProfile.getExpressionFile(e.getKey()) != null) continue; String propertyName = RankingExpression.propertyName(e.getKey()); - if (context.serializedFunctions().containsKey(propertyName)) { - continue; - } + if (context.serializedFunctions().containsKey(propertyName)) continue; + String expressionString = e.getValue().function().getBody().getRoot().toString(new StringBuilder(), context, null, null).toString(); - context.addFunctionSerialization(RankingExpression.propertyName(e.getKey()), expressionString); + context.addFunctionSerialization(propertyName, expressionString); for (Map.Entry<String, TensorType> argumentType : e.getValue().function().argumentTypes().entrySet()) context.addArgumentTypeSerialization(e.getKey(), argumentType.getKey(), argumentType.getValue()); if (e.getValue().function().returnType().isPresent()) @@ -333,7 +336,7 @@ public class RawRankProfile implements RankProfilesConfig.Producer { public List<Pair<String, String>> derive() { List<Pair<String, String>> properties = new ArrayList<>(); for (RankProfile.RankProperty property : rankProperties) { - if ("rankingExpression(firstphase).rankingScript".equals(property.getName())) { + if (("rankingExpression(" + RankProfile.FIRST_PHASE + ").rankingScript").equals(property.getName())) { // Could have been set by function expansion. Set expressions, then skip this property. try { firstPhaseRanking = new RankingExpression(property.getValue()); @@ -341,7 +344,7 @@ public class RawRankProfile implements RankProfilesConfig.Producer { throw new IllegalArgumentException("Could not parse first phase expression", e); } } - else if ("rankingExpression(secondphase).rankingScript".equals(property.getName())) { + else if (("rankingExpression(" + RankProfile.SECOND_PHASE + ").rankingScript").equals(property.getName())) { try { secondPhaseRanking = new RankingExpression(property.getValue()); } catch (ParseException e) { @@ -352,8 +355,8 @@ public class RawRankProfile implements RankProfilesConfig.Producer { properties.add(new Pair<>(property.getName(), property.getValue())); } } - properties.addAll(deriveRankingPhaseRankProperties(firstPhaseRanking, "firstphase")); - properties.addAll(deriveRankingPhaseRankProperties(secondPhaseRanking, "secondphase")); + properties.addAll(deriveRankingPhaseRankProperties(firstPhaseRanking, rankProfile.getFirstPhaseFile(), RankProfile.FIRST_PHASE)); + properties.addAll(deriveRankingPhaseRankProperties(secondPhaseRanking, rankProfile.getSecondPhaseFile(), RankProfile.SECOND_PHASE)); for (FieldRankSettings settings : fieldRankSettings.values()) { properties.addAll(settings.deriveRankProperties()); } @@ -420,7 +423,7 @@ public class RawRankProfile implements RankProfilesConfig.Producer { return properties; } - private List<Pair<String, String>> deriveRankingPhaseRankProperties(RankingExpression expression, String phase) { + private List<Pair<String, String>> deriveRankingPhaseRankProperties(RankingExpression expression, String fileName, String phase) { List<Pair<String, String>> properties = new ArrayList<>(); if (expression == null) return properties; @@ -428,7 +431,9 @@ public class RawRankProfile implements RankProfilesConfig.Producer { if ("".equals(name)) name = phase; - if (expression.getRoot() instanceof ReferenceNode) { + if (useExternalExpressionFiles && (fileName != null)) { + properties.add(new Pair<>("vespa.rank." + phase, "rankingExpression(" + rankProfile.getUniqueExpressionName(name) + ")")); + } else if (expression.getRoot() instanceof ReferenceNode) { properties.add(new Pair<>("vespa.rank." + phase, expression.getRoot().toString())); } else { properties.add(new Pair<>("vespa.rank." + phase, "rankingExpression(" + name + ")")); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java index 6e16b1a120e..8dea1b65079 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/AbstractService.java @@ -92,21 +92,30 @@ public abstract class AbstractService extends AbstractConfigProducer<AbstractCon * Preferred constructor when building from XML. Use this if you are building * in doBuild() in an AbstractConfigProducerBuilder. * build() will call initService() in that case, after setting hostalias and baseport. - * @param parent Parent config producer in the model tree. - * @param name Name of this service. + * + * @param parent the parent config producer in the model tree + * @param name the name of this service */ - public AbstractService(AbstractConfigProducer parent, String name) { + public AbstractService(AbstractConfigProducer<?> parent, String name) { super(parent, name); } /** * Only used for testing. Stay away. - * @param name Name of this service. + * + * @param name the name of this service. */ public AbstractService(String name) { super(name); } + @Override + public void remove() { + super.remove(); + if (hostResource != null) + hostResource.deallocateService(this); + } + /** * Distribute affinity on a collection of services. Services that are located on the same host * will be assigned a specific cpu socket on that host. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java b/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java index a80982fe75b..b6a9d2bf97a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java @@ -6,9 +6,12 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.provision.NetworkPorts; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Optional; import java.util.logging.Level; @@ -36,6 +39,7 @@ public class HostPorts { private PortFinder portFinder = new PortFinder(Collections.emptyList()); + private boolean flushed = false; private Optional<NetworkPorts> networkPortsList = Optional.empty(); public HostPorts(String hostname) { @@ -143,8 +147,16 @@ public class HostPorts { return allocator.result(); } + void deallocatePorts(NetworkPortRequestor service) { + if (flushed) + throw new IllegalStateException("Cannot deallocate ports after calling flushPortReservations()"); + portDB.entrySet().removeIf(entry -> entry.getValue().getServiceName().equals(service.getServiceName())); + allocatedPorts--; + } + public void flushPortReservations() { this.networkPortsList = Optional.of(new NetworkPorts(portFinder.allocations())); + this.flushed = true; } /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java b/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java index 3bc07db9507..ef041d06978 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java @@ -79,6 +79,11 @@ public class HostResource implements Comparable<HostResource> { return ports; } + void deallocateService(AbstractService service) { + hostPorts.deallocatePorts(service); + services.remove(service.getServiceName()); + } + /** * Returns the service with the given "sentinel name" on this Host, * or null if the name does not match any service. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java index 4dc38c09ab1..ab00e9d295f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java @@ -33,6 +33,7 @@ import com.yahoo.container.QrConfig; import com.yahoo.path.Path; import com.yahoo.searchdefinition.OnnxModel; import com.yahoo.searchdefinition.OnnxModels; +import com.yahoo.searchdefinition.RankExpressionFiles; import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.RankingConstants; @@ -129,6 +130,9 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri /** The global ranking constants of this model */ private final RankingConstants rankingConstants = new RankingConstants(); + /** External rank expression files of this */ + private final RankExpressionFiles rankExpressionFiles = new RankExpressionFiles(); + /** The validation overrides of this. This is never null. */ private final ValidationOverrides validationOverrides; @@ -181,15 +185,17 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri createGlobalRankProfiles(deployState.getDeployLogger(), deployState.getImportedModels(), deployState.rankProfileRegistry(), deployState.getQueryProfiles()); - this.rankProfileList = new RankProfileList(null, // null search -> global - rankingConstants, AttributeFields.empty, - deployState.rankProfileRegistry(), - deployState.getQueryProfiles().getRegistry(), - deployState.getImportedModels(), - deployState.getProperties()); + rankProfileList = new RankProfileList(null, // null search -> global + rankingConstants, + rankExpressionFiles, + AttributeFields.empty, + deployState.rankProfileRegistry(), + deployState.getQueryProfiles().getRegistry(), + deployState.getImportedModels(), + deployState.getProperties()); HostSystem hostSystem = root.hostSystem(); - if (complete) { // create a a completed, frozen model + if (complete) { // create a completed, frozen model configModelRepo.readConfigModels(deployState, this, builder, root, configModelRegistry); addServiceClusters(deployState, builder); setupRouting(deployState); @@ -260,6 +266,8 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri /** Returns the global ranking constants of this */ public RankingConstants rankingConstants() { return rankingConstants; } + public RankExpressionFiles rankExpressionFiles() { return rankExpressionFiles; } + /** Creates a mutable model with no services instantiated */ public static VespaModel createIncomplete(DeployState deployState) throws IOException, SAXException { return new VespaModel(new NullConfigModelRegistry(), deployState, false, diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java index d1b9bbb4e58..e080ce43730 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java @@ -148,15 +148,23 @@ public class Admin extends AbstractConfigProducer<Admin> implements Serializable public void setClusterControllers(ClusterControllerContainerCluster clusterControllers, DeployLogger deployLogger) { this.clusterControllers = clusterControllers; - if (isHostedVespa) + if (isHostedVespa) { + // Prefer to put Slobroks on the admin cluster running cluster controllers to avoid unnecessary + // movement of the slobroks when there are changes to the content cluster nodes + removeSlobroks(); addSlobroks(createSlobroksOn(clusterControllers, deployLogger)); + } + } + + private void removeSlobroks() { + slobroks.forEach(Slobrok::remove); + slobroks.clear(); } private List<Slobrok> createSlobroksOn(ClusterControllerContainerCluster clusterControllers, DeployLogger deployLogger) { List<Slobrok> slobroks = new ArrayList<>(); - int index = this.slobroks.size(); for (ClusterControllerContainer clusterController : clusterControllers.getContainers()) { - Slobrok slobrok = new Slobrok(this, index++); + Slobrok slobrok = new Slobrok(this, clusterController.index()); slobrok.setHostResource(clusterController.getHostResource()); slobroks.add(slobrok); slobrok.initService(deployLogger); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java index eed886b707f..00b6d872ddd 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Slobrok.java @@ -9,9 +9,10 @@ import com.yahoo.vespa.model.PortAllocBridge; /** * Represents a Slobrok service. * - * @author gjoranv + * @author gjoranv */ public class Slobrok extends AbstractService implements StateserverConfig.Producer { + private static final long serialVersionUID = 1L; public final static int BASEPORT = 19099; @@ -25,7 +26,7 @@ public class Slobrok extends AbstractService implements StateserverConfig.Produc * @param parent the parent ConfigProducer. * @param index unique index for all slobroks */ - public Slobrok(AbstractConfigProducer parent, int index) { + public Slobrok(AbstractConfigProducer<?> parent, int index) { super(parent, "slobrok." + index); portsMeta.on(0).tag("rpc").tag("admin").tag("status"); portsMeta.on(1).tag("http").tag("state"); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java index 7049213ddab..d87c6596fa4 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java @@ -8,6 +8,8 @@ import com.yahoo.log.InvalidLogFormatException; import com.yahoo.log.LogMessage; import com.yahoo.path.Path; import com.yahoo.searchdefinition.OnnxModel; +import com.yahoo.searchdefinition.RankExpressionFile; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.yolean.Exceptions; import com.yahoo.system.ProcessExecuter; @@ -38,7 +40,6 @@ import java.util.logging.Logger; import java.util.logging.Level; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * Validate rank setup for all search clusters (rank-profiles, index-schema, attributes configs), validating done @@ -116,50 +117,61 @@ public class RankSetupValidator extends Validator { } private void writeConfigs(String dir, AbstractConfigProducer<?> producer) throws IOException { - RankProfilesConfig.Builder rpcb = new RankProfilesConfig.Builder(); - ((RankProfilesConfig.Producer) producer).getConfig(rpcb); - RankProfilesConfig rpc = new RankProfilesConfig(rpcb); - writeConfig(dir, RankProfilesConfig.getDefName() + ".cfg", rpc); - - IndexschemaConfig.Builder iscb = new IndexschemaConfig.Builder(); - ((IndexschemaConfig.Producer) producer).getConfig(iscb); - IndexschemaConfig isc = new IndexschemaConfig(iscb); - writeConfig(dir, IndexschemaConfig.getDefName() + ".cfg", isc); - - AttributesConfig.Builder acb = new AttributesConfig.Builder(); - ((AttributesConfig.Producer) producer).getConfig(acb); - AttributesConfig ac = new AttributesConfig(acb); - writeConfig(dir, AttributesConfig.getDefName() + ".cfg", ac); - - RankingConstantsConfig.Builder rccb = new RankingConstantsConfig.Builder(); - ((RankingConstantsConfig.Producer) producer).getConfig(rccb); - RankingConstantsConfig rcc = new RankingConstantsConfig(rccb); - writeConfig(dir, RankingConstantsConfig.getDefName() + ".cfg", rcc); - - OnnxModelsConfig.Builder omcb = new OnnxModelsConfig.Builder(); - ((OnnxModelsConfig.Producer) producer).getConfig(omcb); - OnnxModelsConfig omc = new OnnxModelsConfig(omcb); - writeConfig(dir, OnnxModelsConfig.getDefName() + ".cfg", omc); - - ImportedFieldsConfig.Builder ifcb = new ImportedFieldsConfig.Builder(); - ((ImportedFieldsConfig.Producer) producer).getConfig(ifcb); - ImportedFieldsConfig ifc = new ImportedFieldsConfig(ifcb); - writeConfig(dir, ImportedFieldsConfig.getDefName() + ".cfg", ifc); + RankProfilesConfig.Builder rpcb = new RankProfilesConfig.Builder(); + ((RankProfilesConfig.Producer) producer).getConfig(rpcb); + RankProfilesConfig rpc = new RankProfilesConfig(rpcb); + writeConfig(dir, RankProfilesConfig.getDefName() + ".cfg", rpc); + + IndexschemaConfig.Builder iscb = new IndexschemaConfig.Builder(); + ((IndexschemaConfig.Producer) producer).getConfig(iscb); + IndexschemaConfig isc = new IndexschemaConfig(iscb); + writeConfig(dir, IndexschemaConfig.getDefName() + ".cfg", isc); + + AttributesConfig.Builder acb = new AttributesConfig.Builder(); + ((AttributesConfig.Producer) producer).getConfig(acb); + AttributesConfig ac = new AttributesConfig(acb); + writeConfig(dir, AttributesConfig.getDefName() + ".cfg", ac); + + RankingConstantsConfig.Builder rccb = new RankingConstantsConfig.Builder(); + ((RankingConstantsConfig.Producer) producer).getConfig(rccb); + RankingConstantsConfig rcc = new RankingConstantsConfig(rccb); + writeConfig(dir, RankingConstantsConfig.getDefName() + ".cfg", rcc); + + RankingExpressionsConfig.Builder recb = new RankingExpressionsConfig.Builder(); + ((RankingExpressionsConfig.Producer) producer).getConfig(recb); + RankingExpressionsConfig rec = new RankingExpressionsConfig(recb); + writeConfig(dir, RankingExpressionsConfig.getDefName() + ".cfg", rec); + + OnnxModelsConfig.Builder omcb = new OnnxModelsConfig.Builder(); + ((OnnxModelsConfig.Producer) producer).getConfig(omcb); + OnnxModelsConfig omc = new OnnxModelsConfig(omcb); + writeConfig(dir, OnnxModelsConfig.getDefName() + ".cfg", omc); + + ImportedFieldsConfig.Builder ifcb = new ImportedFieldsConfig.Builder(); + ((ImportedFieldsConfig.Producer) producer).getConfig(ifcb); + ImportedFieldsConfig ifc = new ImportedFieldsConfig(ifcb); + writeConfig(dir, ImportedFieldsConfig.getDefName() + ".cfg", ifc); } private void writeExtraVerifyRanksetupConfig(String dir, DocumentDatabase db) throws IOException { String configName = "verify-ranksetup.cfg"; String configContent = ""; + List<String> config = new ArrayList<>(); // Assist verify-ranksetup in finding the actual ONNX model files - Map<String, OnnxModel> models = db.getDerivedConfiguration().getSearch().onnxModels().asMap(); - if (models.values().size() > 0) { - List<String> config = new ArrayList<>(models.values().size() * 2); - for (OnnxModel model : models.values()) { - String modelPath = getFileRepositoryPath(model.getFilePath(), model.getFileReference()); - config.add(String.format("file[%d].ref \"%s\"", config.size() / 2, model.getFileReference())); - config.add(String.format("file[%d].path \"%s\"", config.size() / 2, modelPath)); - } + for (OnnxModel model : db.getDerivedConfiguration().getSearch().onnxModels().asMap().values()) { + String modelPath = getFileRepositoryPath(model.getFilePath(), model.getFileReference()); + config.add(String.format("file[%d].ref \"%s\"", config.size() / 2, model.getFileReference())); + config.add(String.format("file[%d].path \"%s\"", config.size() / 2, modelPath)); + } + + for (RankExpressionFile expr : db.getDerivedConfiguration().getSearch().rankExpressionFiles().asMap().values()) { + String modelPath = getFileRepositoryPath(expr.getFilePath(), expr.getFileReference()); + config.add(String.format("file[%d].ref \"%s\"", config.size() / 2, expr.getFileReference())); + config.add(String.format("file[%d].path \"%s\"", config.size() / 2, modelPath)); + } + + if ( ! config.isEmpty() ) { configContent = StringUtilities.implodeMultiline(config); } IOUtils.writeFile(dir + configName, configContent, false); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java index a0673824907..bbec64d45fb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java @@ -67,7 +67,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { admin, allocateHosts(admin.hostSystem(), "slobroks", nodesSpecification)); } - else { // TODO: Remove + else { // These will be removed later, if an admin cluster (for cluster controllers) is assigned createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.minResources().nodes(), 2)); @@ -153,8 +153,8 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { List<HostResource> picked = sortedContainerHostsFrom(model, count, !retired); // if we can return multiple hosts, include retired nodes which would have been picked before - // (probably - assuming all previous nodes were retired, which is always true for a single cluster - // at the moment (Sept 2015)) to ensure a smoother transition between the old and new topology + // (probably - assuming all previous nodes were retired, which is always true for a single cluster, + // to ensure a smoother transition between the old and new topology // by including both new and old nodes during the retirement period picked.addAll(sortedContainerHostsFrom(model, count, retired)); @@ -166,6 +166,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase { List<HostResource> hosts = model.getCluster().getContainers().stream() .filter(container -> retired == container.isRetired()) .map(Container::getHostResource) + .sorted(HostResource::comparePrimarilyByIndexTo) .collect(Collectors.toList()); return hosts.subList(0, Math.min(count, hosts.size())); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/DomFederationSearcherBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/DomFederationSearcherBuilder.java index 3a4e8a70613..36bf792ee82 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/DomFederationSearcherBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/DomFederationSearcherBuilder.java @@ -43,7 +43,6 @@ public class DomFederationSearcherBuilder extends VespaDomBuilder.DomConfigProdu return XML.getChild(searcherSpec, "source-set") != null; } - private List<FederationSearcherModel.TargetSpec> readSources(Element searcherSpec) { List<FederationSearcherModel.TargetSpec> sources = new ArrayList<>(); for (Element source : XML.getChildren(searcherSpec, "source")) { @@ -76,14 +75,14 @@ public class DomFederationSearcherBuilder extends VespaDomBuilder.DomConfigProdu } @Override - protected FederationSearcher doBuild(DeployState deployState, AbstractConfigProducer ancestor, Element searcherElement) { + protected FederationSearcher doBuild(DeployState deployState, AbstractConfigProducer<?> ancestor, Element searcherElement) { FederationSearcherModel model = new FederationSearcherModelBuilder(searcherElement).build(); Optional<Component> targetSelector = buildTargetSelector(deployState, ancestor, searcherElement, model.getComponentId()); return new FederationSearcher(model, targetSelector); } - private Optional<Component> buildTargetSelector(DeployState deployState, AbstractConfigProducer ancestor, Element searcherElement, ComponentId namespace) { + private Optional<Component> buildTargetSelector(DeployState deployState, AbstractConfigProducer<?> ancestor, Element searcherElement, ComponentId namespace) { Element targetSelectorElement = XML.getChild(searcherElement, "target-selector"); if (targetSelectorElement == null) return Optional.empty(); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/SearchChainsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/SearchChainsBuilder.java index 0106123666d..9fb19efbf75 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/SearchChainsBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/chains/search/SearchChainsBuilder.java @@ -16,9 +16,10 @@ import java.util.List; import java.util.Map; /** + * Creates top level search chains(searchchain, provider) from xml. + * * @author Tony Vaagenes * @author gjoranv - * Creates top level search chains(searchchain, provider) from xml. */ public class SearchChainsBuilder extends ChainsBuilder<Searcher<?>, SearchChain> { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java index 4e78f44d0fe..317ed0f66c7 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java @@ -25,6 +25,7 @@ import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainer; import com.yahoo.vespa.model.container.component.BindingPattern; import com.yahoo.vespa.model.container.component.Component; @@ -58,6 +59,7 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat RankProfilesConfig.Producer, RankingConstantsConfig.Producer, OnnxModelsConfig.Producer, + RankingExpressionsConfig.Producer, ServletPathsConfig.Producer, ContainerMbusConfig.Producer, MetricsProxyApiConfig.Producer, @@ -233,6 +235,10 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat if (modelEvaluation != null) modelEvaluation.getConfig(builder); } + public void getConfig(RankingExpressionsConfig.Builder builder) { + if (modelEvaluation != null) modelEvaluation.getConfig(builder); + } + @Override public void getConfig(ContainerMbusConfig.Builder builder) { if (mbusParams != null) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java index 6ef29269bc1..cd596038137 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java @@ -79,7 +79,6 @@ public abstract class Container extends AbstractService implements private final ComponentGroup<Component<?, ?>> components = new ComponentGroup<>(this, "components"); private final JettyHttpServer defaultHttpServer; - private final boolean enableJdiscHttp2; protected Container(AbstractConfigProducer<?> parent, String name, int index, DeployState deployState) { this(parent, name, false, index, deployState); @@ -100,8 +99,6 @@ public abstract class Container extends AbstractService implements addChild(new SimpleComponent("com.yahoo.container.jdisc.ConfiguredApplication$ApplicationContext")); appendJvmOptions(jvmOmitStackTraceInFastThrowOption(deployState.featureFlags())); - - this.enableJdiscHttp2 = deployState.featureFlags().enableJdiscHttp2(); } protected String jvmOmitStackTraceInFastThrowOption(ModelContext.FeatureFlags featureFlags) { @@ -183,7 +180,7 @@ public abstract class Container extends AbstractService implements } private void initDefaultJettyConnector() { - defaultHttpServer.addConnector(new ConnectorFactory.Builder("SearchServer", getSearchPort()).enableHttp2(enableJdiscHttp2).build()); + defaultHttpServer.addConnector(new ConnectorFactory.Builder("SearchServer", getSearchPort()).build()); } private ContainerServiceType myServiceType = null; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java index 510d2fe3d99..37bfb8821c3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerModelEvaluation.java @@ -7,6 +7,7 @@ import com.yahoo.searchdefinition.derived.RankProfileList; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.SystemBindingPattern; @@ -18,10 +19,12 @@ import java.util.Objects; * * @author bratseth */ -public class ContainerModelEvaluation implements RankProfilesConfig.Producer, - RankingConstantsConfig.Producer, - OnnxModelsConfig.Producer -{ + +public class ContainerModelEvaluation implements + RankProfilesConfig.Producer, + RankingConstantsConfig.Producer, + OnnxModelsConfig.Producer, + RankingExpressionsConfig.Producer { private final static String BUNDLE_NAME = "model-evaluation"; private final static String EVALUATOR_NAME = ModelsEvaluator.class.getName(); @@ -38,8 +41,7 @@ public class ContainerModelEvaluation implements RankProfilesConfig.Producer, } public void prepare(List<ApplicationContainer> containers) { - rankProfileList.sendConstantsTo(containers); - rankProfileList.sendOnnxModelsTo(containers); + rankProfileList.sendTo(containers); } @Override @@ -57,6 +59,10 @@ public class ContainerModelEvaluation implements RankProfilesConfig.Producer, rankProfileList.getConfig(builder); } + public void getConfig(RankingExpressionsConfig.Builder builder) { + rankProfileList.getConfig(builder); + } + public static Handler<?> getHandler() { Handler<?> handler = new Handler<>(new ComponentModel(REST_HANDLER_NAME, null, BUNDLE_NAME)); handler.addServerBindings( diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java index 9b0075c79c3..0d024398de7 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java @@ -20,7 +20,6 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig private final String name; private final int listenPort; private final SslProvider sslProviderComponent; - private final boolean enableHttp2; private volatile ComponentId defaultRequestFilterChain; private volatile ComponentId defaultResponseFilterChain; @@ -33,7 +32,6 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig this.sslProviderComponent = builder.sslProvider != null ? builder.sslProvider : new DefaultSslProvider(name); this.defaultRequestFilterChain = builder.defaultRequestFilterChain; this.defaultResponseFilterChain = builder.defaultResponseFilterChain; - this.enableHttp2 = builder.enableHttp2 != null ? builder.enableHttp2 : false; addChild(sslProviderComponent); inject(sslProviderComponent); } @@ -42,7 +40,6 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig public void getConfig(ConnectorConfig.Builder connectorBuilder) { connectorBuilder.listenPort(listenPort); connectorBuilder.name(name); - connectorBuilder.http2Enabled(enableHttp2); sslProviderComponent.amendConnectorConfig(connectorBuilder); } @@ -69,7 +66,6 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig private SslProvider sslProvider; private ComponentId defaultRequestFilterChain; private ComponentId defaultResponseFilterChain; - private Boolean enableHttp2; public Builder(String name, int listenPort) { this.name = name; @@ -88,8 +84,6 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig this.defaultResponseFilterChain = filterChain; return this; } - public Builder enableHttp2(boolean enabled) { this.enableHttp2 = enabled; return this; } - public ConnectorFactory build() { return new ConnectorFactory(this); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java index 9d715073f77..766aa46fc01 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java @@ -29,32 +29,32 @@ public class HostedSslConnectorFactory extends ConnectorFactory { * Create connector factory that uses a certificate provided by the config-model / configserver and default hosted Vespa truststore. */ public static HostedSslConnectorFactory withProvidedCertificate( - String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth, boolean enableHttp2) { + String serverName, EndpointCertificateSecrets endpointCertificateSecrets, boolean enforceHandshakeClientAuth) { ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( serverName, endpointCertificateSecrets, DEFAULT_HOSTED_TRUSTSTORE, /*tlsCaCertificates*/null, enforceHandshakeClientAuth); - return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth, enableHttp2); + return new HostedSslConnectorFactory(sslProvider, false, enforceHandshakeClientAuth); } /** * Create connector factory that uses a certificate provided by the config-model / configserver and a truststore configured by the application. */ public static HostedSslConnectorFactory withProvidedCertificateAndTruststore( - String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates, boolean enableHttp2) { + String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) { ConfiguredDirectSslProvider sslProvider = createConfiguredDirectSslProvider( serverName, endpointCertificateSecrets, /*tlsCaCertificatesPath*/null, tlsCaCertificates, false); - return new HostedSslConnectorFactory(sslProvider, true, false, enableHttp2); + return new HostedSslConnectorFactory(sslProvider, true, false); } /** * Create connector factory that uses the default certificate and truststore provided by Vespa (through Vespa-global TLS configuration). */ - public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String serverName, boolean enableHttp2) { - return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false, enableHttp2); + public static HostedSslConnectorFactory withDefaultCertificateAndTruststore(String serverName) { + return new HostedSslConnectorFactory(new DefaultSslProvider(serverName), true, false); } private HostedSslConnectorFactory(SslProvider sslProvider, boolean enforceClientAuth, - boolean enforceHandshakeClientAuth, boolean enableHttp2) { - super(new Builder("tls4443", 4443).sslProvider(sslProvider).enableHttp2(enableHttp2)); + boolean enforceHandshakeClientAuth) { + super(new Builder("tls4443", 4443).sslProvider(sslProvider)); this.enforceClientAuth = enforceClientAuth; this.enforceHandshakeClientAuth = enforceHandshakeClientAuth; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java index 3b616c34a03..22fa5332d5e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java @@ -39,7 +39,7 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil .map(ComponentId::new) .ifPresent(builder::defaultResponseFilterChain); SslProvider sslProviderComponent = getSslConfigComponents(name, serverSpec); - return builder.sslProvider(sslProviderComponent).enableHttp2(deployState.featureFlags().enableJdiscHttp2()).build(); + return builder.sslProvider(sslProviderComponent).build(); } SslProvider getSslConfigComponents(String serverName, Element serverSpec) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/FederationSearcher.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/FederationSearcher.java index ceb48732116..6b4cb003cda 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/FederationSearcher.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/FederationSearcher.java @@ -11,7 +11,12 @@ import com.yahoo.search.federation.FederationConfig; import com.yahoo.search.searchchain.model.federation.FederationSearcherModel.TargetSpec; import com.yahoo.vespa.model.container.component.Component; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; /** * Config producer for the FederationSearcher. @@ -26,8 +31,8 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem * Generates config for a single search chain contained in a target. */ private static final class SearchChainConfig { + private final SearchChain searchChain; - //Zero if not applicable final ComponentId providerId; final FederationOptions targetOptions; final List<String> documentTypes; @@ -61,6 +66,7 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem * which can be federated to as a single entity. */ private static abstract class Target { + final ComponentId id; final FederationOptions targetOptions; @@ -79,41 +85,36 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem } protected abstract void getSearchChainsConfig(FederationConfig.Target.Builder tb); + } private static class SearchChainTarget extends Target { + private final SearchChainConfig searchChainConfig; - public SearchChainTarget(SearchChain searchChain, - FederationOptions targetOptions) { + public SearchChainTarget(SearchChain searchChain, FederationOptions targetOptions) { super(searchChain.getComponentId(), targetOptions); - searchChainConfig = new SearchChainConfig( - searchChain, - null, - targetOptions, - searchChain.getDocumentTypes()); + searchChainConfig = new SearchChainConfig(searchChain, null, targetOptions, searchChain.getDocumentTypes()); } @Override protected void getSearchChainsConfig(FederationConfig.Target.Builder tB) { tB.searchChain(searchChainConfig.getSearchChainConfig()); } + } private static class SourceGroupTarget extends Target { + private final SearchChainConfig leaderConfig; - private final List<SearchChainConfig> participantsConfig = - new ArrayList<>(); + private final List<SearchChainConfig> participantsConfig = new ArrayList<>(); - public SourceGroupTarget(SourceGroup group, - FederationOptions targetOptions) { + public SourceGroupTarget(SourceGroup group, FederationOptions targetOptions) { super(group.getComponentId(), applyDefaultSourceGroupOptions(targetOptions)); leaderConfig = createConfig(group.leader(), targetOptions); - for (Source participant : group.participants()) { - participantsConfig.add( - createConfig(participant, targetOptions)); - } + for (Source participant : group.participants()) + participantsConfig.add(createConfig(participant, targetOptions)); } private static FederationOptions applyDefaultSourceGroupOptions(FederationOptions targetOptions) { @@ -121,64 +122,49 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem return targetOptions.inherit(defaultSourceGroupOption); } - private SearchChainConfig createConfig(Source source, - FederationOptions targetOptions) { - return new SearchChainConfig( - source, - source.getParentProvider().getComponentId(), - targetOptions, - source.getDocumentTypes()); + private SearchChainConfig createConfig(Source source, FederationOptions targetOptions) { + return new SearchChainConfig(source, + source.getParentProvider().getComponentId(), + targetOptions, + source.getDocumentTypes()); } @Override protected void getSearchChainsConfig(FederationConfig.Target.Builder tB) { tB.searchChain(leaderConfig.getSearchChainConfig()); - for (SearchChainConfig participant : participantsConfig) { + for (SearchChainConfig participant : participantsConfig) tB.searchChain(participant.getSearchChainConfig()); - } } } private static class TargetResolver { + final ComponentRegistry<SearchChain> searchChainRegistry; final SourceGroupRegistry sourceGroupRegistry; - /** - * @return true if searchChain.id newer than sourceGroup.id - */ - private boolean newerVersion(SearchChain searchChain, - SourceGroup sourceGroup) { - if (searchChain == null || sourceGroup == null) { - return false; - } else { - return newerVersion(searchChain.getComponentId(), sourceGroup.getComponentId()); - } + /** Returns true if searchChain.id newer than sourceGroup.id */ + private boolean newerVersion(SearchChain searchChain, SourceGroup sourceGroup) { + if (searchChain == null || sourceGroup == null) return false; + return newerVersion(searchChain.getComponentId(), sourceGroup.getComponentId()); } - /** - * @return true if a newer than b - */ + /** Returns true if a newer than b */ private boolean newerVersion(ComponentId a, ComponentId b) { return a.compareTo(b) > 0; } - - TargetResolver(ComponentRegistry<SearchChain> searchChainRegistry, - SourceGroupRegistry sourceGroupRegistry) { + TargetResolver(ComponentRegistry<SearchChain> searchChainRegistry, SourceGroupRegistry sourceGroupRegistry) { this.searchChainRegistry = searchChainRegistry; this.sourceGroupRegistry = sourceGroupRegistry; } Target resolve(FederationSearcherModel.TargetSpec specification) { - SearchChain searchChain = searchChainRegistry.getComponent( - specification.sourceSpec); - SourceGroup sourceGroup = sourceGroupRegistry.getComponent( - specification.sourceSpec); + SearchChain searchChain = searchChainRegistry.getComponent(specification.sourceSpec); + SourceGroup sourceGroup = sourceGroupRegistry.getComponent(specification.sourceSpec); if (searchChain == null && sourceGroup == null) { return null; - } else if (sourceGroup == null || - newerVersion(searchChain, sourceGroup)) { + } else if (sourceGroup == null || newerVersion(searchChain, sourceGroup)) { return new SearchChainTarget(searchChain, specification.federationOptions); } else { return new SourceGroupTarget(sourceGroup, specification.federationOptions); @@ -186,26 +172,21 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem } } - private final Map<ComponentId, Target> resolvedTargets = - new LinkedHashMap<>(); + private final Map<ComponentId, Target> resolvedTargets = new LinkedHashMap<>(); public FederationSearcher(FederationSearcherModel searcherModel, Optional<Component> targetSelector) { super(searcherModel); this.targetSelector = targetSelector; - if (targetSelector.isPresent()) - addChild(targetSelector.get()); + targetSelector.ifPresent(selector -> addChild(selector)); } @Override public void getConfig(FederationConfig.Builder builder) { - for (Target target : resolvedTargets.values()) { + for (Target target : resolvedTargets.values()) builder.target(target.getTargetConfig()); - } - if (targetSelector.isPresent()) { - builder.targetSelector(targetSelector.get().getGlobalComponentId().stringValue()); - } + targetSelector.ifPresent(selector -> builder.targetSelector(selector.getGlobalComponentId().stringValue())); } @Override @@ -213,10 +194,8 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem initialize(getSearchChains().allChains(), getSearchChains().allSourceGroups()); } - void initialize(ComponentRegistry<SearchChain> searchChainRegistry, - SourceGroupRegistry sourceGroupRegistry) { - TargetResolver targetResolver = new TargetResolver( - searchChainRegistry, sourceGroupRegistry); + void initialize(ComponentRegistry<SearchChain> searchChainRegistry, SourceGroupRegistry sourceGroupRegistry) { + TargetResolver targetResolver = new TargetResolver(searchChainRegistry, sourceGroupRegistry); addSourceTargets(targetResolver, model.targets); @@ -229,16 +208,14 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem Target target = targetResolver.resolve(targetSpec); if (target == null) { - throw new RuntimeException("Can't find source " + - targetSpec.sourceSpec + - " used as a source for federation '" + - getComponentId() + "'"); + throw new RuntimeException("Can't find source " + targetSpec.sourceSpec + + " used as a source for federation '" + getComponentId() + "'"); } Target duplicate = resolvedTargets.put(target.id, target); if (duplicate != null && !duplicate.targetOptions.equals(target.targetOptions)) { - throw new RuntimeException("Search chain " + target.id + " added twice with different federation options" - + " to the federation searcher " + getComponentId()); + throw new RuntimeException("Search chain " + target.id + " added twice with different federation options" + + " to the federation searcher " + getComponentId()); } } } @@ -248,23 +225,21 @@ public class FederationSearcher extends Searcher<FederationSearcherModel> implem for (GenericTarget genericTarget : defaultTargets(searchChainRegistry.allComponents())) { ComponentSpecification specification = genericTarget.getComponentId().toSpecification(); - //Can't use genericTarget directly, as it might be part of a source group. + // Can't use genericTarget directly, as it might be part of a source group. Target federationTarget = targetResolver.resolve(new TargetSpec(specification, new FederationOptions())); - //Do not replace manually added sources, as they might have manually configured federation options + // Do not replace manually added sources, as they might have manually configured federation options if (!resolvedTargets.containsKey(federationTarget.id)) resolvedTargets.put(federationTarget.id, federationTarget); } } - private static List<GenericTarget> defaultTargets(Collection<SearchChain> allSearchChains) { - Collection<Provider> providers = - CollectionUtil.filter(allSearchChains, Provider.class); + Collection<Provider> providers = CollectionUtil.filter(allSearchChains, Provider.class); List<GenericTarget> defaultTargets = new ArrayList<>(); - for (Provider provider : providers) { + for (Provider provider : providers) defaultTargets.addAll(provider.defaultFederationTargets()); - } return defaultTargets; } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/Provider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/Provider.java index ee4edf3fd8c..10e0f3e55da 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/Provider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/Provider.java @@ -7,6 +7,7 @@ import com.yahoo.vespa.model.container.component.ConfigProducerGroup; import java.util.Arrays; import java.util.Collection; +import java.util.List; /** * Base config producer for search chains that communicate with backends. @@ -15,7 +16,7 @@ import java.util.Collection; */ public class Provider extends GenericTarget { - private ConfigProducerGroup<Source> sources; + private final ConfigProducerGroup<Source> sources; public Provider(ChainSpecification specWithoutInnerSearchers, FederationOptions federationOptions) { super(specWithoutInnerSearchers, federationOptions); @@ -37,9 +38,10 @@ public class Provider extends GenericTarget { public Collection<? extends GenericTarget> defaultFederationTargets() { if (sources.getComponents().isEmpty()) { - return Arrays.asList(this); + return List.of(this); } else { return sources.getComponents(); } } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 87406b8bc9a..b477587bcac 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -431,7 +431,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { // If the deployment contains certificate/private key reference, setup TLS port HostedSslConnectorFactory connectorFactory; - boolean enableHttp2 = deployState.featureFlags().enableJdiscHttp2(); if (deployState.endpointCertificateSecrets().isPresent()) { boolean authorizeClient = deployState.zone().system().isPublic(); if (authorizeClient && deployState.tlsClientAuthority().isEmpty()) { @@ -445,10 +444,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { .orElse(false); connectorFactory = authorizeClient - ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get(), enableHttp2) - : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets, enforceHandshakeClientAuth, enableHttp2); + ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get()) + : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets, enforceHandshakeClientAuth); } else { - connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName, enableHttp2); + connectorFactory = HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName); } cluster.getHttp().getAccessControl().ifPresent(accessControl -> accessControl.configureHostedConnector(connectorFactory)); server.addConnector(connectorFactory); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterResourceLimits.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterResourceLimits.java index c7d56c9b4b5..70f2acd3c7b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterResourceLimits.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterResourceLimits.java @@ -1,6 +1,7 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; +import com.yahoo.config.application.api.DeployLogger; import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import com.yahoo.vespa.model.content.cluster.DomResourceLimitsBuilder; @@ -35,28 +36,31 @@ public class ClusterResourceLimits { public static class Builder { private final boolean enableFeedBlockInDistributor; + private final boolean hostedVespa; + private final DeployLogger deployLogger; private ResourceLimits.Builder ctrlBuilder = new ResourceLimits.Builder(); private ResourceLimits.Builder nodeBuilder = new ResourceLimits.Builder(); - public Builder(boolean enableFeedBlockInDistributor) { + public Builder(boolean enableFeedBlockInDistributor, boolean hostedVespa, DeployLogger deployLogger) { this.enableFeedBlockInDistributor = enableFeedBlockInDistributor; + this.hostedVespa = hostedVespa; + this.deployLogger = deployLogger; } public ClusterResourceLimits build(ModelElement clusterElem) { - ModelElement tuningElem = clusterElem.childByPath("tuning"); - if (tuningElem != null) { - ctrlBuilder = DomResourceLimitsBuilder.createBuilder(tuningElem); - } - - ModelElement protonElem = clusterElem.childByPath("engine.proton"); - if (protonElem != null) { - nodeBuilder = DomResourceLimitsBuilder.createBuilder(protonElem); - } + ctrlBuilder = createBuilder(clusterElem.childByPath("tuning")); + nodeBuilder = createBuilder(clusterElem.childByPath("engine.proton")); deriveLimits(); return new ClusterResourceLimits(this); } + private ResourceLimits.Builder createBuilder(ModelElement element) { + return element == null + ? new ResourceLimits.Builder() + : DomResourceLimitsBuilder.createBuilder(element, hostedVespa, deployLogger); + } + public void setClusterControllerBuilder(ResourceLimits.Builder builder) { ctrlBuilder = builder; } @@ -88,7 +92,7 @@ public class ClusterResourceLimits { Optional<Double> contentNodeLimit, Consumer<Double> setter) { // TODO: remove this when feed block in distributor is default enabled. - if (!clusterControllerLimit.isPresent() && !contentNodeLimit.isPresent()) { + if (clusterControllerLimit.isEmpty() && contentNodeLimit.isEmpty()) { setter.accept(0.8); } } @@ -96,7 +100,7 @@ public class ClusterResourceLimits { private void deriveClusterControllerLimit(Optional<Double> clusterControllerLimit, Optional<Double> contentNodeLimit, Consumer<Double> setter) { - if (!clusterControllerLimit.isPresent()) { + if (clusterControllerLimit.isEmpty()) { contentNodeLimit.ifPresent(limit -> // TODO: emit warning when feed block in distributor is default enabled. setter.accept(Double.max(0.0, (limit - 0.01)))); @@ -106,7 +110,7 @@ public class ClusterResourceLimits { private void deriveContentNodeLimit(Optional<Double> contentNodeLimit, Optional<Double> clusterControllerLimit, Consumer<Double> setter) { - if (!contentNodeLimit.isPresent()) { + if (contentNodeLimit.isEmpty()) { clusterControllerLimit.ifPresent(limit -> setter.accept(calcContentNodeLimit(limit))); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java index f6a45842bd9..b5cae857ce0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java @@ -25,7 +25,6 @@ import com.yahoo.vespa.config.content.core.BucketspacesConfig; import com.yahoo.vespa.config.content.core.StorDistributormanagerConfig; import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.admin.Admin; -import com.yahoo.vespa.model.admin.Configserver; import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerCluster; import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerComponent; import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerConfigurer; @@ -134,7 +133,10 @@ public class ContentCluster extends AbstractConfigProducer implements globallyDistributedDocuments, routingSelection, deployState.zone(), deployState.isHosted()); boolean enableFeedBlockInDistributor = deployState.getProperties().featureFlags().enableFeedBlockInDistributor(); - var resourceLimits = new ClusterResourceLimits.Builder(enableFeedBlockInDistributor).build(contentElement); + var resourceLimits = new ClusterResourceLimits.Builder(enableFeedBlockInDistributor, + stateIsHosted(deployState), + deployState.getDeployLogger()) + .build(contentElement); c.clusterControllerConfig = new ClusterControllerConfig.Builder(getClusterId(contentElement), contentElement, resourceLimits.getClusterControllerLimits()).build(deployState, c, contentElement.getXml()); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomResourceLimitsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomResourceLimitsBuilder.java index 210f062f9b2..9f4852629d0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomResourceLimitsBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/DomResourceLimitsBuilder.java @@ -1,9 +1,12 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content.cluster; +import com.yahoo.config.application.api.DeployLogger; import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import com.yahoo.vespa.model.content.ResourceLimits; +import java.util.logging.Level; + /** * Builder for feed block resource limits. * @@ -11,10 +14,16 @@ import com.yahoo.vespa.model.content.ResourceLimits; */ public class DomResourceLimitsBuilder { - public static ResourceLimits.Builder createBuilder(ModelElement contentXml) { + public static ResourceLimits.Builder createBuilder(ModelElement contentXml, boolean hostedVespa, DeployLogger deployLogger) { ResourceLimits.Builder builder = new ResourceLimits.Builder(); ModelElement resourceLimits = contentXml.child("resource-limits"); - if (resourceLimits == null) { + if (resourceLimits == null) { return builder; } + + if (hostedVespa) { + deployLogger.logApplicationPackage(Level.WARNING, "Element " + resourceLimits + + " is not allowed, default limits will be used"); + // TODO: Throw exception when we are sure nobody is using this + //throw new IllegalArgumentException("Element " + element + " is not allowed to be set, default limits will be used"); return builder; } if (resourceLimits.child("disk") != null) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java index fb9402614f6..5bb57f4ff6c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/filedistribution/FileDistributor.java @@ -8,7 +8,12 @@ import com.yahoo.config.application.api.FileRegistry; import com.yahoo.vespa.model.ConfigProxy; import com.yahoo.vespa.model.Host; -import java.util.*; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Sends RPC requests to hosts (tenant hosts and config servers) asking them to start download of files. This is used diff --git a/config-model/src/main/java/com/yahoo/vespa/model/routing/DocumentProtocol.java b/config-model/src/main/java/com/yahoo/vespa/model/routing/DocumentProtocol.java index 15e6c5993b3..fbba377c950 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/routing/DocumentProtocol.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/routing/DocumentProtocol.java @@ -111,6 +111,8 @@ public final class DocumentProtocol implements Protocol, addSelector(cluster.getConfigId(), cluster.getRoutingSelector(), clusterBuilder); if (cluster.getSearch().hasIndexedCluster()) addRoutes(getDirectRouteName(cluster.getConfigId()), getIndexedRouteName(cluster.getConfigId()), clusterBuilder); + else + clusterBuilder.defaultRoute(cluster.getConfigId()); builder.cluster(cluster.getConfigId(), clusterBuilder); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java index 705c48d1c9b..3ca5b9e3a0a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/AbstractSearchCluster.java @@ -39,8 +39,7 @@ public abstract class AbstractSearchCluster extends AbstractConfigProducer<Abstr public void prepareToDistributeFiles(List<SearchNode> backends) { for (SchemaSpec sds : localSDS) { - sds.getSearchDefinition().getSearch().rankingConstants().sendTo(backends); - sds.getSearchDefinition().getSearch().onnxModels().sendTo(backends); + sds.getSearchDefinition().getSearch().sendTo(backends); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java b/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java index 8d4c1675bbf..9f0ff7db9f8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java @@ -12,6 +12,7 @@ import com.yahoo.vespa.config.search.SummaryConfig; import com.yahoo.vespa.config.search.SummarymapConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.config.search.summary.JuniperrcConfig; import com.yahoo.vespa.configdefinition.IlscriptsConfig; @@ -26,6 +27,7 @@ public class DocumentDatabase extends AbstractConfigProducer<DocumentDatabase> i AttributesConfig.Producer, RankProfilesConfig.Producer, RankingConstantsConfig.Producer, + RankingExpressionsConfig.Producer, OnnxModelsConfig.Producer, IndexschemaConfig.Producer, JuniperrcConfig.Producer, @@ -75,6 +77,11 @@ public class DocumentDatabase extends AbstractConfigProducer<DocumentDatabase> i } @Override + public void getConfig(RankingExpressionsConfig.Builder builder) { + derivedCfg.getRankProfileList().getConfig(builder); + } + + @Override public void getConfig(RankingConstantsConfig.Builder builder) { derivedCfg.getRankProfileList().getConfig(builder); } diff --git a/config-model/src/main/javacc/SDParser.jj b/config-model/src/main/javacc/SDParser.jj index 8f99c388fb1..0cc1db5a2d7 100644 --- a/config-model/src/main/javacc/SDParser.jj +++ b/config-model/src/main/javacc/SDParser.jj @@ -79,16 +79,6 @@ public class SDParser { private RankProfileRegistry rankProfileRegistry; private boolean documentsOnly; - /** For testing only */ - public SDParser(String input, DeployLogger deployLogger) { - this(new SimpleCharStream(input), deployLogger); - } - - /** For testing only */ - public SDParser(SimpleCharStream stream, DeployLogger deployLogger) { - this(stream, deployLogger, MockApplicationPackage.createEmpty(), new RankProfileRegistry(), false); - } - /** * Creates a parser * @@ -435,7 +425,7 @@ Search rootSchema(String dir) : Search search; } { - ( ( <SCHEMA> | <SEARCH> ) name = identifier() { search = new Search(name, app); + ( ( <SCHEMA> | <SEARCH> ) name = identifier() { search = new Search(name, app, deployLogger); rankProfileRegistry.add(new DefaultRankProfile(search, rankProfileRegistry)); rankProfileRegistry.add(new UnrankedRankProfile(search, rankProfileRegistry));} lbrace() (rootSchemaItem(search) (<NL>)*)* <RBRACE> (<NL>)* <EOF>) @@ -476,7 +466,7 @@ Object rootSchemaItem(Search search) : { } */ Search rootDocument(String dir) : { - Search search = new DocumentOnlySearch(); + Search search = new DocumentOnlySearch(app, deployLogger); } { ( (rootDocumentItem(search) (<NL>)*)*<EOF> ) diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java index 326bf577acc..2b72420614d 100644 --- a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java @@ -494,9 +494,8 @@ public class ModelProvisioningTest { Set<HostResource> clusterControllerHosts = admin.getClusterControllers().getContainers() .stream().map(cc -> cc.getHostResource()).collect(Collectors.toSet()); Set<HostResource> slobrokHosts = admin.getSlobroks().stream().map(Slobrok::getHost).collect(Collectors.toSet()); - assertEquals(6, slobrokHosts.size()); - assertTrue("Slobroks are assigned from container and cluster controller nodes", - union(containerHosts, clusterControllerHosts).containsAll(slobrokHosts)); + assertEquals(3, slobrokHosts.size()); + assertTrue("Slobroks are assigned on cluster controller nodes", clusterControllerHosts.containsAll(slobrokHosts)); assertTrue("Logserver is assigned from container nodes", containerHosts.contains(admin.getLogserver().getHost())); assertEquals("No in-cluster config servers in a hosted environment", 0, admin.getConfigservers().size()); assertEquals("Dedicated admin cluster controllers when hosted", 3, admin.getClusterControllers().getContainers().size()); @@ -560,6 +559,40 @@ public class ModelProvisioningTest { } @Test + public void testSlobroksOnContainersIfNoContentClusters() { + String services = + "<?xml version='1.0' encoding='utf-8' ?>\n" + + "<services>" + + " <admin version='4.0'/>" + + " <container version='1.0' id='foo'>" + + " <nodes count='10'/>" + + " </container>" + + "</services>"; + + int numberOfHosts = 10; + VespaModelTester tester = new VespaModelTester(); + tester.addHosts(numberOfHosts); + VespaModel model = tester.createModel(services, true); + assertEquals(numberOfHosts, model.getRoot().hostSystem().getHosts().size()); + + // Check container cluster + assertEquals(1, model.getContainerClusters().size()); + Set<HostResource> containerHosts = model.getContainerClusters().get("foo").getContainers().stream() + .map(Container::getHost) + .collect(Collectors.toSet()); + assertEquals(10, containerHosts.size()); + + // Check admin clusters + Admin admin = model.getAdmin(); + Set<HostResource> slobrokHosts = admin.getSlobroks().stream().map(Slobrok::getHost).collect(Collectors.toSet()); + assertEquals(3, slobrokHosts.size()); + assertTrue("Slobroks are assigned from container nodes", + containerHosts.containsAll(slobrokHosts)); + assertTrue("Logserver is assigned from container nodes", containerHosts.contains(admin.getLogserver().getHost())); + assertEquals("No in-cluster config servers in a hosted environment", 0, admin.getConfigservers().size()); + } + + @Test public void testUsingNodesAndGroupCountAttributesWithoutDedicatedClusterControllers() { String services = "<?xml version='1.0' encoding='utf-8' ?>\n" + @@ -602,9 +635,8 @@ public class ModelProvisioningTest { Set<HostResource> clusterControllerHosts = admin.getClusterControllers().getContainers() .stream().map(cc -> cc.getHostResource()).collect(Collectors.toSet()); Set<HostResource> slobrokHosts = admin.getSlobroks().stream().map(Slobrok::getHost).collect(Collectors.toSet()); - assertEquals(6, slobrokHosts.size()); - assertTrue("Slobroks are assigned from container and cluster controller nodes", - union(containerHosts, clusterControllerHosts).containsAll(slobrokHosts)); + assertEquals(3, slobrokHosts.size()); + assertTrue("Slobroks are assigned on cluster controller nodes", clusterControllerHosts.containsAll(slobrokHosts)); assertTrue("Logserver is assigned from container nodes", containerHosts.contains(admin.getLogserver().getHost())); assertEquals("No in-cluster config servers in a hosted environment", 0, admin.getConfigservers().size()); assertEquals(3, admin.getClusterControllers().getContainers().size()); @@ -1613,7 +1645,7 @@ public class ModelProvisioningTest { tester.addHosts(6); VespaModel model = tester.createModel(services, true); assertEquals(6, model.getRoot().hostSystem().getHosts().size()); - assertEquals(5, model.getAdmin().getSlobroks().size()); + assertEquals(3, model.getAdmin().getSlobroks().size()); assertEquals(2, model.getContainerClusters().get("foo").getContainers().size()); assertEquals(1, model.getContentClusters().get("bar").getRootGroup().countNodes(true)); } @@ -2056,9 +2088,6 @@ public class ModelProvisioningTest { " </nodes>", " <engine>", " <proton>", - " <resource-limits>", - " <memory>0.92</memory>", - " </resource-limits>", " <tuning>", " <searchnode>", " <flushstrategy>", @@ -2084,7 +2113,6 @@ public class ModelProvisioningTest { assertEquals(2000, cfg.flush().memory().maxtlssize()); // from config override assertEquals(1000, cfg.flush().memory().maxmemory()); // from explicit tuning assertEquals((long) ((128 - reservedMemoryGb) * GB / 8), cfg.flush().memory().each().maxmemory()); // from default node flavor tuning - assertEquals(0.92, cfg.writefilter().memorylimit(), 0.0001); // from explicit resource-limits } private static ProtonConfig getProtonConfig(VespaModel model, String configId) { @@ -2138,11 +2166,4 @@ public class ModelProvisioningTest { assertProvisioned(nodeCount, id, null, type, model); } - private Set<HostResource> union(Set<HostResource> a, Set<HostResource> b) { - Set<HostResource> union = new HashSet<>(); - union.addAll(a); - union.addAll(b); - return union; - } - } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/DocumentGraphValidatorTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/DocumentGraphValidatorTest.java index c2dd3e45dea..f475fbe6d78 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/DocumentGraphValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/DocumentGraphValidatorTest.java @@ -135,7 +135,7 @@ public class DocumentGraphValidatorTest { } private static Search createSearchWithName(String name, Search... parents) { - Search campaignSearch = new Search(name, null); + Search campaignSearch = new Search(name); SDDocumentType document = new SDDocumentType(name); campaignSearch.addDocument(document); document.setDocumentReferences(new DocumentReferences(Collections.emptyMap())); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/DocumentReferenceResolverTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/DocumentReferenceResolverTest.java index e46208c770d..6b65303fe56 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/DocumentReferenceResolverTest.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/DocumentReferenceResolverTest.java @@ -23,14 +23,16 @@ import static org.junit.Assert.assertTrue; */ public class DocumentReferenceResolverTest { + private static final String BAR = "bar"; + private static final String FOO = "foo"; @Rule public final ExpectedException exceptionRule = ExpectedException.none(); @Test public void reference_from_one_document_to_another_is_resolved() { // Create bar document with no fields - Search barSearch = new Search(); - SDDocumentType barDocument = new SDDocumentType("bar", barSearch); + Search barSearch = new Search(BAR); + SDDocumentType barDocument = new SDDocumentType(BAR, barSearch); barSearch.addDocument(barDocument); // Create foo document with document reference to bar and add another field @@ -38,7 +40,7 @@ public class DocumentReferenceResolverTest { ("bar_ref", ReferenceDataType.createWithInferredId(barDocument.getDocumentType())); AttributeUtils.addAttributeAspect(fooRefToBarField); SDField irrelevantField = new SDField("irrelevant_stuff", DataType.INT); - Search fooSearch = new Search(); + Search fooSearch = new Search(FOO); SDDocumentType fooDocument = new SDDocumentType("foo", fooSearch); fooDocument.addField(fooRefToBarField); fooDocument.addField(irrelevantField); @@ -60,7 +62,7 @@ public class DocumentReferenceResolverTest { SDField fooRefToBarField = new SDField( "bar_ref", ReferenceDataType.createWithInferredId(TemporaryStructuredDataType.create("bar"))); AttributeUtils.addAttributeAspect(fooRefToBarField); - Search fooSearch = new Search(); + Search fooSearch = new Search(FOO); SDDocumentType fooDocument = new SDDocumentType("foo", fooSearch); fooDocument.addField(fooRefToBarField); fooSearch.addDocument(fooDocument); @@ -76,14 +78,14 @@ public class DocumentReferenceResolverTest { @Test public void throws_exception_if_reference_is_not_an_attribute() { // Create bar document with no fields - Search barSearch = new Search(); + Search barSearch = new Search(BAR); SDDocumentType barDocument = new SDDocumentType("bar", barSearch); barSearch.addDocument(barDocument); // Create foo document with document reference to bar SDField fooRefToBarField = new SDField ("bar_ref", ReferenceDataType.createWithInferredId(barDocument.getDocumentType())); - Search fooSearch = new Search(); + Search fooSearch = new Search(FOO); SDDocumentType fooDocument = new SDDocumentType("foo", fooSearch); fooDocument.addField(fooRefToBarField); fooSearch.addDocument(fooDocument); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/ImportedFieldsEnumeratorTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/ImportedFieldsEnumeratorTest.java index fcbb89b5c42..f12129155b1 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/ImportedFieldsEnumeratorTest.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/ImportedFieldsEnumeratorTest.java @@ -18,30 +18,33 @@ public class ImportedFieldsEnumeratorTest { @Test public void imported_fields_are_enumerated_and_copied_from_correct_search_instance() { - Search parentSearch = new Search(); - SDDocumentType parentDocument = new SDDocumentType("parent", parentSearch); + String PARENT = "parent"; + Search parentSearch = new Search(PARENT); + SDDocumentType parentDocument = new SDDocumentType(PARENT, parentSearch); var parentField = new SDField("their_field", DataType.INT); AttributeUtils.addAttributeAspect(parentField); parentDocument.addField(parentField); parentSearch.addDocument(parentDocument); - Search fooSearch = new Search(); + String FOO = "foo"; + Search fooSearch = new Search(FOO); SDField fooRefToParent = new SDField( "foo_ref", ReferenceDataType.createWithInferredId(parentDocument.getDocumentType())); AttributeUtils.addAttributeAspect(fooRefToParent); var fooImports = fooSearch.temporaryImportedFields().get(); fooImports.add(new TemporaryImportedField("my_first_import", "foo_ref", "their_field")); fooImports.add(new TemporaryImportedField("my_second_import", "foo_ref", "their_field")); - SDDocumentType fooDocument = new SDDocumentType("foo", fooSearch); + SDDocumentType fooDocument = new SDDocumentType(FOO, fooSearch); fooSearch.addDocument(fooDocument); - Search barSearch = new Search(); + String BAR = "bar"; + Search barSearch = new Search(BAR); SDField barRefToParent = new SDField( "bar_ref", ReferenceDataType.createWithInferredId(parentDocument.getDocumentType())); AttributeUtils.addAttributeAspect(barRefToParent); var barImports = barSearch.temporaryImportedFields().get(); barImports.add(new TemporaryImportedField("my_cool_import", "my_ref", "their_field")); - SDDocumentType barDocument = new SDDocumentType("bar", barSearch); + SDDocumentType barDocument = new SDDocumentType(BAR, barSearch); barSearch.addDocument(barDocument); var enumerator = new ImportedFieldsEnumerator(List.of(parentSearch, fooSearch, barSearch)); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileRegistryTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileRegistryTest.java index 02ec597c3ed..84819439048 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileRegistryTest.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileRegistryTest.java @@ -33,7 +33,7 @@ public class RankProfileRegistryTest { @Test(expected = IllegalArgumentException.class) public void testRankProfileDuplicateNameIsIllegal() { - Search search = new Search("foo", null); + Search search = new Search("foo"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); RankProfile barRankProfile = new RankProfile("bar", search, rankProfileRegistry); rankProfileRegistry.add(barRankProfile); @@ -42,7 +42,7 @@ public class RankProfileRegistryTest { @Test public void testRankProfileDuplicateNameLegalForOverridableRankProfiles() { - Search search = new Search("foo", null); + Search search = new Search("foo"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); for (String rankProfileName : RankProfileRegistry.overridableRankProfileNames) { diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java index 0f83a4db98f..91e8640308a 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java @@ -38,7 +38,7 @@ public class RankProfileTestCase extends SchemaTestCase { @Test public void testRankProfileInheritance() { - Search search = new Search("test", null); + Search search = new Search("test"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); SDDocumentType document = new SDDocumentType("test"); SDField a = document.addField("a", DataType.STRING); @@ -173,7 +173,7 @@ public class RankProfileTestCase extends SchemaTestCase { " rank-profile p1 {}\n" + " rank-profile p2 {}\n" + "}"); - builder.build(true, new BaseDeployLogger()); + builder.build(true); Search search = builder.getSearch(); assertEquals(4, registry.all().size()); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/EmptyRankProfileTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/EmptyRankProfileTestCase.java index 47862a2611b..139dae65c63 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/EmptyRankProfileTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/EmptyRankProfileTestCase.java @@ -21,7 +21,7 @@ public class EmptyRankProfileTestCase extends SchemaTestCase { @Test public void testDeriving() { - Search search = new Search("test", null); + Search search = new Search("test"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); SDDocumentType doc = new SDDocumentType("test"); search.addDocument(doc); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java index 770bea55af2..370315ad08b 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.derived; +import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.parser.ParseException; import org.junit.Test; @@ -103,7 +104,8 @@ public class ExportingTestCase extends AbstractExportingTestCase { @Test public void testRankExpression() throws IOException, ParseException { - assertCorrectDeriving("rankexpression"); + assertCorrectDeriving("rankexpression", null, + new TestProperties().useExternalRankExpression(true), new TestableDeployLogger()); } @Test diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/IdTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/IdTestCase.java index 130450e483d..b2cac8cd4c2 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/IdTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/IdTestCase.java @@ -23,7 +23,7 @@ public class IdTestCase extends AbstractExportingTestCase { @Test public void testExplicitUpperCaseIdField() { - Search search = new Search("test", null); + Search search = new Search("test"); SDDocumentType document = new SDDocumentType("test"); search.addDocument(document); SDField uri = new SDField("URI", DataType.URI); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java index 1b51fd354f3..c0a8a47e4ee 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java @@ -143,7 +143,7 @@ public class InheritanceTestCase extends AbstractExportingTestCase { @Test public void testIndexSettingInheritance() { SDDocumentType parent = new SDDocumentType("parent"); - Search parentSearch = new Search("parent", null); + Search parentSearch = new Search("parent"); parentSearch.addDocument(parent); SDField prefixed = parent.addField("prefixed", DataType.STRING); prefixed.parseIndexingScript("{ index }"); @@ -151,7 +151,7 @@ public class InheritanceTestCase extends AbstractExportingTestCase { SDDocumentType child = new SDDocumentType("child"); child.inherit(parent); - Search childSearch = new Search("child", null); + Search childSearch = new Search("child"); childSearch.addDocument(child); prefixed = (SDField)child.getField("prefixed"); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/LiteralBoostTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/LiteralBoostTestCase.java index be720cbe4dd..2e36319bd8c 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/LiteralBoostTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/LiteralBoostTestCase.java @@ -30,7 +30,7 @@ public class LiteralBoostTestCase extends AbstractExportingTestCase { */ @Test public void testLiteralBoost() { - Search search=new Search("literalboost", null); + Search search=new Search("literalboost"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); SDDocumentType document=new SDDocumentType("literalboost"); search.addDocument(document); @@ -62,7 +62,7 @@ public class LiteralBoostTestCase extends AbstractExportingTestCase { */ @Test public void testNonDefaultRankLiteralBoost() { - Search search=new Search("literalboost", null); + Search search=new Search("literalboost"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); SDDocumentType document=new SDDocumentType("literalboost"); search.addDocument(document); @@ -88,7 +88,7 @@ public class LiteralBoostTestCase extends AbstractExportingTestCase { /** Tests literal boosts in two fields going to the same index */ @Test public void testTwoLiteralBoostFields() { - Search search=new Search("msb", null); + Search search=new Search("msb"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); SDDocumentType document=new SDDocumentType("msb"); search.addDocument(document); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/SearchOrdererTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/SearchOrdererTestCase.java index 0c677456a87..f72f67f3711 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/SearchOrdererTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/SearchOrdererTestCase.java @@ -62,7 +62,7 @@ public class SearchOrdererTestCase extends SchemaTestCase { } private static Search createSchema(String name, Map<String, Search> schemas) { - Search search = new Search(name, null); + Search search = new Search(name); SDDocumentType document = new SDDocumentType(name); document.setDocumentReferences(new DocumentReferences(emptyMap())); search.addDocument(document); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java index 07d7405b1db..24d694cd442 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java @@ -72,7 +72,7 @@ public class SummaryMapTestCase extends SchemaTestCase { } @Test public void testPositionDeriving() { - Search search = new Search("store", null); + Search search = new Search("store"); SDDocumentType document = new SDDocumentType("store"); search.addDocument(document); String fieldName = "location"; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/TypeConversionTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/TypeConversionTestCase.java index c03e915aa8b..364eb9dc014 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/TypeConversionTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/TypeConversionTestCase.java @@ -25,7 +25,7 @@ public class TypeConversionTestCase extends SchemaTestCase { /** Tests that exact-string stuff is not spilled over to the default index */ @Test public void testExactStringToStringTypeConversion() { - Search search = new Search("test", null); + Search search = new Search("test"); RankProfileRegistry rankProfileRegistry = RankProfileRegistry.createRankProfileRegistryWithBuiltinRankProfiles(search); SDDocumentType document = new SDDocumentType("test"); search.addDocument(document); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/VsmFieldsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/VsmFieldsTestCase.java index 37b49955bbf..b813f08d49e 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/VsmFieldsTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/VsmFieldsTestCase.java @@ -20,7 +20,7 @@ public class VsmFieldsTestCase { @Test public void reference_type_field_is_unsearchable() { - Search search = new Search("test", MockApplicationPackage.createEmpty()); + Search search = new Search("test", MockApplicationPackage.createEmpty(), new TestableDeployLogger()); search.addDocument(new SDDocumentType("test")); SDField refField = new TemporarySDField("ref_field", ReferenceDataType.createWithInferredId(TemporaryStructuredDataType.create("parent_type"))); refField.parseIndexingScript("{ summary }"); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java index defaf565a8b..83949ea6f8d 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFieldsTest.java @@ -6,6 +6,7 @@ import com.yahoo.document.DataType; import com.yahoo.document.Field; import com.yahoo.searchdefinition.DocumentReference; import com.yahoo.searchdefinition.Search; +import com.yahoo.searchdefinition.derived.TestableDeployLogger; import com.yahoo.searchdefinition.document.ImportedField; import com.yahoo.searchdefinition.document.ImportedFields; import com.yahoo.searchdefinition.document.ImportedSimpleField; @@ -44,14 +45,14 @@ public class AddAttributeTransformToSummaryOfImportedFieldsTest { } private static Search createSearchWithDocument(String documentName) { - Search search = new Search(documentName, MockApplicationPackage.createEmpty()); + Search search = new Search(documentName, MockApplicationPackage.createEmpty(), new TestableDeployLogger()); SDDocumentType document = new SDDocumentType(documentName, search); search.addDocument(document); return search; } private static ImportedFields createSingleImportedField(String fieldName) { - Search targetSearch = new Search("target_doc", MockApplicationPackage.createEmpty()); + Search targetSearch = new Search("target_doc", MockApplicationPackage.createEmpty(), new TestableDeployLogger()); SDField targetField = new SDField("target_field", DataType.INT); DocumentReference documentReference = new DocumentReference(new Field("reference_field"), targetSearch); ImportedField importedField = new ImportedSimpleField(fieldName, documentReference, targetField); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingScriptRewriterTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingScriptRewriterTestCase.java index 7863c544b60..833dbc14a53 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingScriptRewriterTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingScriptRewriterTestCase.java @@ -153,7 +153,7 @@ public class IndexingScriptRewriterTestCase extends SchemaTestCase { private static ScriptExpression processField(SDField unprocessedField) { SDDocumentType sdoc = new SDDocumentType("test"); sdoc.addField(unprocessedField); - Search search = new Search("test", null); + Search search = new Search("test"); search.addDocument(sdoc); new Processing().process(search, new BaseDeployLogger(), new RankProfileRegistry(), new QueryProfiles(), true, false); return unprocessedField.getIndexingScript(); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ParentChildSearchModel.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ParentChildSearchModel.java index 7d6a8507dd3..ecafd7a4222 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ParentChildSearchModel.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ParentChildSearchModel.java @@ -10,6 +10,7 @@ import com.yahoo.document.TemporaryStructuredDataType; import com.yahoo.searchdefinition.DocumentReference; import com.yahoo.searchdefinition.DocumentReferences; import com.yahoo.searchdefinition.Search; +import com.yahoo.searchdefinition.derived.TestableDeployLogger; import com.yahoo.searchdefinition.document.SDDocumentType; import com.yahoo.searchdefinition.document.SDField; import com.yahoo.searchdefinition.document.TemporaryImportedField; @@ -29,7 +30,7 @@ public class ParentChildSearchModel { } protected Search createSearch(String name) { - Search result = new Search(name, app); + Search result = new Search(name, app, new TestableDeployLogger()); result.addDocument(new SDDocumentType(name)); return result; } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankProfileSearchFixture.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankProfileSearchFixture.java index e6616ce0dd1..683242c1333 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankProfileSearchFixture.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankProfileSearchFixture.java @@ -4,6 +4,7 @@ package com.yahoo.searchdefinition.processing; import com.google.common.collect.ImmutableList; import com.yahoo.config.application.api.ApplicationPackage; import ai.vespa.rankingexpression.importer.configmodelview.MlModelImporter; +import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.path.Path; import com.yahoo.search.query.profile.QueryProfileRegistry; @@ -62,7 +63,7 @@ class RankProfileSearchFixture { String rankProfiles, String constant, String field) throws ParseException { this.queryProfileRegistry = queryProfileRegistry; - SearchBuilder builder = new SearchBuilder(applicationpackage, rankProfileRegistry, queryProfileRegistry); + SearchBuilder builder = new SearchBuilder(applicationpackage, new BaseDeployLogger(), rankProfileRegistry, queryProfileRegistry); String sdContent = "search test {\n" + " " + (constant != null ? constant : "") + "\n" + " document test {\n" + diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolverTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolverTestCase.java index 12fe7e151c0..e3d81be6743 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolverTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeResolverTestCase.java @@ -384,7 +384,7 @@ public class RankingExpressionTypeResolverTestCase { @Test public void undeclaredQueryFeaturesAreAccepted() throws Exception { InspectableDeployLogger logger = new InspectableDeployLogger(); - SearchBuilder builder = new SearchBuilder(); + SearchBuilder builder = new SearchBuilder(logger); builder.importString(joinLines( "search test {", " document test { ", @@ -401,8 +401,8 @@ public class RankingExpressionTypeResolverTestCase { " }", " }", "}" - ), logger); - builder.build(true, logger); + )); + builder.build(true); String message = logger.findMessage("The following query features"); assertNull(message); } @@ -410,7 +410,7 @@ public class RankingExpressionTypeResolverTestCase { @Test public void undeclaredQueryFeaturesAreAcceptedWithWarningWhenUsingTensors() throws Exception { InspectableDeployLogger logger = new InspectableDeployLogger(); - SearchBuilder builder = new SearchBuilder(); + SearchBuilder builder = new SearchBuilder(logger); builder.importString(joinLines( "search test {", " document test { ", @@ -427,8 +427,8 @@ public class RankingExpressionTypeResolverTestCase { " }", " }", "}" - ), logger); - builder.build(true, logger); + )); + builder.build(true); String message = logger.findMessage("The following query features"); assertNotNull(message); assertEquals("WARNING: The following query features used in 'my_rank_profile' are not declared in query profile types and " + @@ -439,7 +439,7 @@ public class RankingExpressionTypeResolverTestCase { @Test public void noWarningWhenUsingTensorsWhenQueryFeaturesAreDeclared() throws Exception { InspectableDeployLogger logger = new InspectableDeployLogger(); - SearchBuilder builder = new SearchBuilder(); + SearchBuilder builder = new SearchBuilder(logger); QueryProfileType myType = new QueryProfileType("mytype"); myType.addField(new FieldDescription("rank.feature.query(foo)", new TensorFieldType(TensorType.fromSpec("tensor(d[2])"))), @@ -467,8 +467,8 @@ public class RankingExpressionTypeResolverTestCase { " }", " }", "}" - ), logger); - builder.build(true, logger); + )); + builder.build(true); String message = logger.findMessage("The following query features"); assertNull(message); } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTransformerTokensTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTransformerTokensTestCase.java index c64dbcdef03..2ae6ffbf343 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTransformerTokensTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTransformerTokensTestCase.java @@ -1,6 +1,7 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.processing; +import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.search.query.profile.QueryProfileRegistry; import com.yahoo.searchdefinition.RankProfile; @@ -84,7 +85,7 @@ public class RankingExpressionWithTransformerTokensTestCase { " document test {}\n" + " rank-profile my_profile inherits default {}\n" + "}"; - SearchBuilder searchBuilder = new SearchBuilder(application, rankProfileRegistry, queryProfileRegistry); + SearchBuilder searchBuilder = new SearchBuilder(application, new BaseDeployLogger(), rankProfileRegistry, queryProfileRegistry); searchBuilder.importString(sdContent); searchBuilder.build(); Search search = searchBuilder.getSearch(); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionsTestCase.java index 8861432d97b..0695ae6a249 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionsTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionsTestCase.java @@ -2,16 +2,22 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.collections.Pair; +import com.yahoo.config.FileReference; import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.search.query.profile.QueryProfileRegistry; -import com.yahoo.searchdefinition.*; +import com.yahoo.searchdefinition.RankProfile; +import com.yahoo.searchdefinition.RankProfileRegistry; +import com.yahoo.searchdefinition.SchemaTestCase; +import com.yahoo.searchdefinition.Search; +import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.derived.DerivedConfiguration; import com.yahoo.searchdefinition.derived.AttributeFields; import com.yahoo.searchdefinition.derived.RawRankProfile; import com.yahoo.searchdefinition.derived.TestableDeployLogger; import com.yahoo.searchdefinition.parser.ParseException; import ai.vespa.rankingexpression.importer.configmodelview.ImportedMlModels; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import org.junit.Test; import java.io.IOException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java index eecab3c03d7..b0c65b3ea76 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java @@ -21,7 +21,7 @@ public class ReservedRankingExpressionFunctionNamesTestCase { public void requireThatFunctionsWithReservedNamesIssueAWarning() throws ParseException { TestDeployLogger deployLogger = new TestDeployLogger(); RankProfileRegistry rankProfileRegistry = new RankProfileRegistry(); - SearchBuilder builder = new SearchBuilder(rankProfileRegistry); + SearchBuilder builder = new SearchBuilder(deployLogger, rankProfileRegistry); builder.importString( "search test {\n" + " document test { \n" + @@ -50,7 +50,7 @@ public class ReservedRankingExpressionFunctionNamesTestCase { " }\n" + " }\n" + "}\n"); - builder.build(true, deployLogger); + builder.build(true); assertTrue(deployLogger.log.contains("sigmoid") && deployLogger.log.contains("test_rank_profile")); assertTrue(deployLogger.log.contains("sigmoid") && deployLogger.log.contains("test_rank_profile_2")); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java index 8308b638497..6ab74ef2eae 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java @@ -195,7 +195,7 @@ public class TensorTransformTestCase extends SchemaTestCase { " }\n" + " }\n" + "}\n"); - builder.build(true, new BaseDeployLogger()); + builder.build(true); Search s = builder.getSearch(); RankProfile test = rankProfileRegistry.get(s, "test").compile(queryProfiles, new ImportedMlModels()); List<Pair<String, String>> testRankProperties = new RawRankProfile(test, diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java index cec313f98d8..28e70130e75 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ValidateFieldTypesTest.java @@ -6,6 +6,7 @@ import com.yahoo.document.DataType; import com.yahoo.document.Field; import com.yahoo.searchdefinition.DocumentReference; import com.yahoo.searchdefinition.Search; +import com.yahoo.searchdefinition.derived.TestableDeployLogger; import com.yahoo.searchdefinition.document.ImportedField; import com.yahoo.searchdefinition.document.ImportedFields; import com.yahoo.searchdefinition.document.ImportedSimpleField; @@ -45,14 +46,14 @@ public class ValidateFieldTypesTest { } private static Search createSearchWithDocument(String documentName) { - Search search = new Search(documentName, MockApplicationPackage.createEmpty()); + Search search = new Search(documentName, MockApplicationPackage.createEmpty(), new TestableDeployLogger()); SDDocumentType document = new SDDocumentType(documentName, search); search.addDocument(document); return search; } private static ImportedFields createSingleImportedField(String fieldName, DataType dataType) { - Search targetSearch = new Search("target_doc", MockApplicationPackage.createEmpty()); + Search targetSearch = new Search("target_doc", MockApplicationPackage.createEmpty(), new TestableDeployLogger()); SDField targetField = new SDField("target_field", dataType); DocumentReference documentReference = new DocumentReference(new Field("reference_field"), targetSearch); ImportedField importedField = new ImportedSimpleField(fieldName, documentReference, targetField); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java index 27a32f3e754..f9ccdb1a2aa 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model.application.validation.change.search; import com.yahoo.config.application.api.ValidationId; -import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import org.junit.Test; @@ -11,9 +10,9 @@ import java.time.Instant; import java.util.Arrays; import java.util.List; +import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newRefeedAction; import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newReindexAction; import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newRestartAction; -import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newRefeedAction; public class DocumentDatabaseChangeValidatorTest { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ClusterResourceLimitsTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ClusterResourceLimitsTest.java index da2d5e2e3a0..469e4649c14 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/ClusterResourceLimitsTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ClusterResourceLimitsTest.java @@ -1,6 +1,11 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; +import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.config.model.application.provider.BaseDeployLogger; +import com.yahoo.searchdefinition.derived.TestableDeployLogger; +import com.yahoo.text.XML; +import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import org.junit.Test; import java.util.Optional; @@ -43,7 +48,7 @@ public class ClusterResourceLimitsTest { return this; } public ClusterResourceLimits build() { - var builder = new ClusterResourceLimits.Builder(enableFeedBlockInDistributor); + var builder = new ClusterResourceLimits.Builder(enableFeedBlockInDistributor, false, new BaseDeployLogger()); builder.setClusterControllerBuilder(ctrlBuilder); builder.setContentNodeBuilder(nodeBuilder); return builder.build(); @@ -114,6 +119,29 @@ public class ClusterResourceLimitsTest { new Fixture(true)); } + @Test + // TODO: Change to expect exception being thrown when no one uses this in hosted + public void default_resource_limits_when_hosted_and_warning_is_logged() { + TestableDeployLogger logger = new TestableDeployLogger(); + final boolean hosted = true; + + ClusterResourceLimits.Builder builder = new ClusterResourceLimits.Builder(true, hosted, logger); + ClusterResourceLimits limits = builder.build(new ModelElement(XML.getDocument("<cluster id=\"test\">" + + " <tuning>\n" + + " <resource-limits>\n" + + " <memory>0.92</memory>\n" + + " </resource-limits>\n" + + " </tuning>\n" + + "</cluster>") + .getDocumentElement())); + + assertLimits(0.8, 0.8, limits.getClusterControllerLimits()); + assertLimits(0.9, 0.9, limits.getContentNodeLimits()); + + assertEquals(1, logger.warnings.size()); + assertEquals("Element resource-limits is not allowed, default limits will be used", logger.warnings.get(0)); + } + private void assertLimits(Double expCtrlDisk, Double expCtrlMemory, Double expNodeDisk, Double expNodeMemory, Fixture f) { var limits = f.build(); assertLimits(expCtrlDisk, expCtrlMemory, limits.getClusterControllerLimits()); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java index b1bd44d93b4..22e38b30959 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java @@ -1,6 +1,7 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.content; +import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.vespa.config.content.FleetcontrollerConfig; @@ -22,9 +23,12 @@ public class FleetControllerClusterTest { MockRoot root = new MockRoot("", deployState); var clusterElement = new ModelElement(doc.getDocumentElement()); return new ClusterControllerConfig.Builder("storage", - clusterElement, - new ClusterResourceLimits.Builder(enableFeedBlockInDistributor).build(clusterElement).getClusterControllerLimits()). - build(root.getDeployState(), root, clusterElement.getXml()); + clusterElement, + new ClusterResourceLimits.Builder(enableFeedBlockInDistributor, + false, + new BaseDeployLogger()) + .build(clusterElement).getClusterControllerLimits()) + .build(root.getDeployState(), root, clusterElement.getXml()); } private ClusterControllerConfig parse(String xml) { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/ml/ModelEvaluationTest.java b/config-model/src/test/java/com/yahoo/vespa/model/ml/ModelEvaluationTest.java index bbf59fc66e4..bdae01d5e09 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/ml/ModelEvaluationTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/ml/ModelEvaluationTest.java @@ -14,6 +14,7 @@ import com.yahoo.tensor.TensorType; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.container.ApplicationContainerCluster; import org.junit.Test; @@ -96,6 +97,10 @@ public class ModelEvaluationTest { cluster.getConfig(cb); RankingConstantsConfig constantsConfig = new RankingConstantsConfig(cb); + RankingExpressionsConfig.Builder ce = new RankingExpressionsConfig.Builder(); + cluster.getConfig(ce); + RankingExpressionsConfig expressionsConfig = ce.build(); + OnnxModelsConfig.Builder ob = new OnnxModelsConfig.Builder(); cluster.getConfig(ob); OnnxModelsConfig onnxModelsConfig = new OnnxModelsConfig(ob); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java index ea4b3db5ebb..66b60ec3782 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java @@ -40,7 +40,7 @@ public class SearchClusterTest { public void testSdConfigLogical() { // sd1 SDDocumentType sdt1 = new SDDocumentType("s1"); - Search search1 = new Search("s1", null); + Search search1 = new Search("s1"); SDField f1 = new SDField("f1", DataType.STRING); f1.addAttribute(new Attribute("f1", DataType.STRING)); f1.setIndexingScript(new ScriptExpression(new StatementExpression(new AttributeExpression("f1")))); @@ -49,7 +49,7 @@ public class SearchClusterTest { // sd2 SDDocumentType sdt2 = new SDDocumentType("s2"); - Search search2 = new Search("s2", null); + Search search2 = new Search("s2"); SDField f2=new SDField("f2", DataType.STRING); f2.addAttribute(new Attribute("f2", DataType.STRING)); f2.setIndexingScript(new ScriptExpression(new StatementExpression(new AttributeExpression("f2")))); diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java index eb462c86f4f..c3b2bd9f69a 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java @@ -7,6 +7,7 @@ import com.yahoo.config.provisioning.FlavorsConfig; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java index 0548bc7520f..4454517d5ec 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java @@ -280,6 +280,17 @@ public class NodeResources { public boolean isUnspecified() { return this.equals(unspecified); } + // Returns squared euclidean distance of the relevant numerical values of two node resources + public double distanceTo(NodeResources other) { + if ( ! this.diskSpeed().compatibleWith(other.diskSpeed())) return Double.MAX_VALUE; + if ( ! this.storageType().compatibleWith(other.storageType())) return Double.MAX_VALUE; + + double distance = Math.pow(this.vcpu() - other.vcpu(), 2) + Math.pow(this.memoryGb() - other.memoryGb(), 2); + if (this.storageType() == StorageType.local || other.storageType() == StorageType.local) + distance += Math.pow(this.diskGb() - other.diskGb(), 2); + return distance; + } + /** Returns this.isUnspecified() ? Optional.empty() : Optional.of(this) */ public Optional<NodeResources> asOptional() { return this.isUnspecified() ? Optional.empty() : Optional.of(this); diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterResourcesTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterResourcesTest.java new file mode 100644 index 00000000000..2ae5347da12 --- /dev/null +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterResourcesTest.java @@ -0,0 +1,20 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author bratseth + */ +public class ClusterResourcesTest { + + @Test + public void testCost() { + ClusterResources r1 = new ClusterResources(3, 1, new NodeResources(2, 8, 50, 1)); + ClusterResources r2 = new ClusterResources(3, 1, new NodeResources(2, 16, 50, 1)); + assertEquals(1.818, r1.cost() + r2.cost(), 0.01); + } + +} diff --git a/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java b/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java index 608e9fe18b4..910f7a1402b 100644 --- a/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java +++ b/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java @@ -1,7 +1,6 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.hosted.client; -import org.apache.hc.client5.http.classic.methods.ClassicHttpRequests; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.http.ClassicHttpRequest; @@ -12,6 +11,7 @@ import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.HttpEntities; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.net.URIBuilder; import java.io.IOException; @@ -49,7 +49,9 @@ public abstract class AbstractConfigServerClient implements ConfigServerClient { Throwable thrown = null; for (URI host : builder.hosts) { - ClassicHttpRequest request = ClassicHttpRequests.create(builder.method, concat(host, builder.uriBuilder)); + ClassicHttpRequest request = ClassicRequestBuilder.create(builder.method.name()) + .setUri(concat(host, builder.uriBuilder)) + .build(); request.setEntity(builder.entity); try { try { diff --git a/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java b/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java index b2f17f43f5c..956bc90380f 100644 --- a/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java +++ b/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java @@ -89,7 +89,7 @@ class HttpConfigServerClientTest { () -> client.send(HostStrategy.repeating(URI.create("http://localhost:" + server.port()), 10), Method.GET) .read(String::new)); - assertEquals("GET / failed with status 409 and body 'hi'", thrown.getMessage()); + assertEquals("GET http://localhost:" + server.port() + "/ failed with status 409 and body 'hi'", thrown.getMessage()); server.verify(1, getRequestedFor(urlEqualTo("/"))); server.verify(1, anyRequestedFor(anyUrl())); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java index 2dd655c91d8..a6e15223f74 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java @@ -13,14 +13,13 @@ import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.slime.Cursor; import com.yahoo.vespa.config.server.http.JSONResponse; import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; -import org.apache.hc.client5.http.async.methods.SimpleHttpRequests; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpStatus; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.hc.core5.util.TimeValue; @@ -152,7 +151,7 @@ public class ConfigConvergenceChecker extends AbstractComponent { /** Get service generation of service at given URL */ private CompletableFuture<Long> getServiceGeneration(CloseableHttpAsyncClient client, URI serviceUrl, Duration timeout) { - SimpleHttpRequest request = SimpleHttpRequests.get(createApiUri(serviceUrl)); + SimpleHttpRequest request = SimpleRequestBuilder.get(createApiUri(serviceUrl)).build(); request.setConfig(createRequestConfig(timeout)); // Ignoring returned Future object as we want to use the more flexible CompletableFuture instead diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/DefaultClusterReindexingStatusClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/DefaultClusterReindexingStatusClient.java index 1887bf1db9d..65a960e38f4 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/DefaultClusterReindexingStatusClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/DefaultClusterReindexingStatusClient.java @@ -10,8 +10,8 @@ import com.yahoo.config.model.api.PortInfo; import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.vespa.applicationmodel.ClusterId; import com.yahoo.vespa.config.server.modelfactory.ModelResult; -import org.apache.hc.client5.http.async.methods.SimpleHttpRequests; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.core5.concurrent.FutureCallback; @@ -80,7 +80,7 @@ public class DefaultClusterReindexingStatusClient implements ClusterReindexingSt private CompletableFuture<Map<String, ClusterReindexing>> getReindexingStatus(ServiceInfo service) { URI uri = URI.create(String.format("http://%s:%d/reindexing/v1/status", service.getHostName(), getStatePort(service))); CompletableFuture<SimpleHttpResponse> responsePromise = new CompletableFuture<>(); - httpClient.execute(SimpleHttpRequests.get(uri), new FutureCallback<>() { + httpClient.execute(SimpleRequestBuilder.get(uri).build(), new FutureCallback<>() { @Override public void completed(SimpleHttpResponse result) { responsePromise.complete(result); } @Override public void failed(Exception ex) { responsePromise.completeExceptionally(ex); } @Override public void cancelled() { responsePromise.cancel(false); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/InfraDeployerProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/InfraDeployerProvider.java index bc15121e190..1631774112b 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/InfraDeployerProvider.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/InfraDeployerProvider.java @@ -1,4 +1,4 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.deploy; import com.yahoo.cloud.config.ConfigserverConfig; @@ -50,10 +50,7 @@ public class InfraDeployerProvider { /** Creates either an empty provider or a provider having the given infrastructure deployer */ public static InfraDeployerProvider from(Optional<InfraDeployer> infraDeployer) { - if (infraDeployer.isPresent()) - return withInfraDeployer(infraDeployer.get()); - else - return empty(); + return infraDeployer.map(InfraDeployerProvider::withInfraDeployer).orElseGet(InfraDeployerProvider::empty); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 16bcca2ea9c..f44694f5066 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -174,8 +174,8 @@ public class ModelContextImpl implements ModelContext { private final boolean tenantIamRole; private final int maxActivationInhibitedOutOfSyncGroups; private final ToIntFunction<ClusterSpec.Type> jvmOmitStackTraceInFastThrow; - private final boolean enableJdiscHttp2; private final boolean enableCustomAclMapping; + private final boolean useExternalRankExpression; private final int numDistributorStripes; public FeatureFlags(FlagSource source, ApplicationId appId) { @@ -197,9 +197,9 @@ public class ModelContextImpl implements ModelContext { this.tenantIamRole = flagValue(source, appId.tenant(), Flags.TENANT_IAM_ROLE); this.maxActivationInhibitedOutOfSyncGroups = flagValue(source, appId, Flags.MAX_ACTIVATION_INHIBITED_OUT_OF_SYNC_GROUPS); this.jvmOmitStackTraceInFastThrow = type -> flagValueAsInt(source, appId, type, PermanentFlags.JVM_OMIT_STACK_TRACE_IN_FAST_THROW); - this.enableJdiscHttp2 = flagValue(source, appId, Flags.ENABLE_JDISC_HTTP2); this.enableCustomAclMapping = flagValue(source, appId, Flags.ENABLE_CUSTOM_ACL_MAPPING); this.numDistributorStripes = flagValue(source, appId, Flags.NUM_DISTRIBUTOR_STRIPES); + this.useExternalRankExpression = flagValue(source, appId, Flags.USE_EXTERNAL_RANK_EXPRESSION);; } @Override public Optional<NodeResources> dedicatedClusterControllerFlavor() { return Optional.ofNullable(dedicatedClusterControllerFlavor); } @@ -222,9 +222,9 @@ public class ModelContextImpl implements ModelContext { @Override public String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) { return translateJvmOmitStackTraceInFastThrowIntToString(jvmOmitStackTraceInFastThrow, type); } - @Override public boolean enableJdiscHttp2() { return enableJdiscHttp2; } @Override public boolean enableCustomAclMapping() { return enableCustomAclMapping; } @Override public int numDistributorStripes() { return numDistributorStripes; } + @Override public boolean useExternalRankExpressions() { return useExternalRankExpression; } private static <V> V flagValue(FlagSource source, ApplicationId appId, UnboundFlag<? extends V, ?, ?> flag) { return flag.bindTo(source) diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java index 858a2c84e02..1ab99db8985 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.deploy; import com.yahoo.component.Version; @@ -17,6 +17,7 @@ import com.yahoo.path.Path; import com.yahoo.vespa.config.ConfigDefinitionKey; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.config.server.zookeeper.ZKApplicationPackage; +import com.yahoo.yolean.Exceptions; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -27,6 +28,12 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import static com.yahoo.config.application.api.ApplicationPackage.*; +import static com.yahoo.vespa.config.server.zookeeper.ConfigCurator.DEFCONFIGS_ZK_SUBPATH; +import static com.yahoo.vespa.config.server.zookeeper.ConfigCurator.META_ZK_PATH; +import static com.yahoo.vespa.config.server.zookeeper.ConfigCurator.USERAPP_ZK_SUBPATH; +import static com.yahoo.vespa.config.server.zookeeper.ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH; + /** * A class used for reading and writing application data to zookeeper. * @@ -36,62 +43,30 @@ public class ZooKeeperClient { private final ConfigCurator configCurator; private final DeployLogger logger; - /* This is the generation that will be used for reading and writing application data. (1 more than last deployed application) */ - private final Path rootPath; + private final Path sessionPath; // session id private static final ApplicationFile.PathFilter xmlFilter = path -> path.getName().endsWith(".xml"); - public ZooKeeperClient(ConfigCurator configCurator, DeployLogger logger, Path rootPath) { + public ZooKeeperClient(ConfigCurator configCurator, DeployLogger logger, Path sessionPath) { this.configCurator = configCurator; this.logger = logger; - this.rootPath = rootPath; + this.sessionPath = sessionPath; } /** * Sets up basic node structure in ZooKeeper and purges old data. - * This is the first operation on ZK during deploy-application. - * - * We have retries in this method because there have been cases of stray connection loss to ZK, - * even though the user has started the config server. - * + * This is the first operation on ZK during deploy. */ - void setupZooKeeper() { - int retries = 5; - try { - while (retries > 0) { - try { - createZooKeeperNodes(); - break; - } catch (RuntimeException e) { - logger.log(Level.FINE, "ZK init failed, retrying: " + e); - retries--; - if (retries == 0) { - throw e; - } - Thread.sleep(100); - // Not reconnecting, ZK is supposed to handle that automatically - // as long as the session doesn't expire. We'll see. - } - } - } catch (Exception e) { - throw new IllegalStateException("Unable to initialize vespa model writing to config server(s) " + - System.getProperty("configsources") + "\n" + - "Please ensure that cloudconfig_server is started on the config server node(s), " + - "and check the vespa log for configserver errors. ", e); - } - } - - /** Sets the app id and attempts to set up zookeeper. The app id must be ordered for purge to work OK. */ - private void createZooKeeperNodes() { - if ( ! configCurator.exists(rootPath.getAbsolute())) - configCurator.createNode(rootPath.getAbsolute()); + void initialize() { + if ( ! configCurator.exists(sessionPath.getAbsolute())) + configCurator.createNode(sessionPath.getAbsolute()); - for (String subPath : Arrays.asList(ConfigCurator.DEFCONFIGS_ZK_SUBPATH, - ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH, - ConfigCurator.USERAPP_ZK_SUBPATH, + for (String subPath : Arrays.asList(DEFCONFIGS_ZK_SUBPATH, + USER_DEFCONFIGS_ZK_SUBPATH, + USERAPP_ZK_SUBPATH, ZKApplicationPackage.fileRegistryNode)) { // TODO: The replaceFirst below is hackish. - configCurator.createNode(getZooKeeperAppPath(null).getAbsolute(), + configCurator.createNode(getZooKeeperAppPath().getAbsolute(), subPath.replaceFirst("/", "")); } } @@ -110,7 +85,7 @@ public class ZooKeeperClient { write(app.getMetaData()); } catch (Exception e) { throw new IllegalStateException("Unable to write vespa model to config server(s) " + System.getProperty("configsources") + "\n" + - "Please ensure that cloudconfig_server is started on the config server node(s), " + + "Please ensure that config server is started " + "and check the vespa log for configserver errors. ", e); } } @@ -120,7 +95,8 @@ public class ZooKeeperClient { if (sds.isEmpty()) return; // TODO: Change to SCHEMAS_DIR after March 2020 - Path zkPath = getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SEARCH_DEFINITIONS_DIR); + // TODO: When it does also check RankExpressionFile.sendTo + Path zkPath = getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(ApplicationPackage.SEARCH_DEFINITIONS_DIR); configCurator.createNode(zkPath.getAbsolute()); // Ensures that ranking expressions and other files are also written writeDir(app.getFile(ApplicationPackage.SEARCH_DEFINITIONS_DIR), zkPath, false); @@ -139,38 +115,34 @@ public class ZooKeeperClient { */ private void writeSomeOf(ApplicationPackage app) throws IOException { // TODO: We should have a way of doing this which doesn't require repeating all the content - writeFile(app.getFile(Path.fromString(ApplicationPackage.SERVICES)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - writeFile(app.getFile(Path.fromString(ApplicationPackage.HOSTS)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - writeFile(app.getFile(Path.fromString(ApplicationPackage.DEPLOYMENT_FILE.getName())), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - writeFile(app.getFile(Path.fromString(ApplicationPackage.VALIDATION_OVERRIDES.getName())), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH)); - writeDir(app.getFile(ApplicationPackage.RULES_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.RULES_DIR), + writeFile(app.getFile(Path.fromString(SERVICES)), getZooKeeperAppPath(USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(HOSTS)), getZooKeeperAppPath(USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(DEPLOYMENT_FILE.getName())), getZooKeeperAppPath(USERAPP_ZK_SUBPATH)); + writeFile(app.getFile(Path.fromString(VALIDATION_OVERRIDES.getName())), getZooKeeperAppPath(USERAPP_ZK_SUBPATH)); + writeDir(app.getFile(RULES_DIR), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(RULES_DIR), (path) -> path.getName().endsWith(ApplicationPackage.RULES_NAME_SUFFIX), true); - writeDir(app.getFile(ApplicationPackage.QUERY_PROFILES_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.QUERY_PROFILES_DIR), + writeDir(app.getFile(QUERY_PROFILES_DIR), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(QUERY_PROFILES_DIR), xmlFilter, true); - writeDir(app.getFile(ApplicationPackage.PAGE_TEMPLATES_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.PAGE_TEMPLATES_DIR), + writeDir(app.getFile(PAGE_TEMPLATES_DIR), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(PAGE_TEMPLATES_DIR), xmlFilter, true); - writeDir(app.getFile(Path.fromString(ApplicationPackage.SEARCHCHAINS_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SEARCHCHAINS_DIR), + writeDir(app.getFile(Path.fromString(SEARCHCHAINS_DIR)), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(SEARCHCHAINS_DIR), xmlFilter, true); - writeDir(app.getFile(Path.fromString(ApplicationPackage.DOCPROCCHAINS_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.DOCPROCCHAINS_DIR), + writeDir(app.getFile(Path.fromString(DOCPROCCHAINS_DIR)), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(DOCPROCCHAINS_DIR), xmlFilter, true); - writeDir(app.getFile(Path.fromString(ApplicationPackage.ROUTINGTABLES_DIR)), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.ROUTINGTABLES_DIR), + writeDir(app.getFile(Path.fromString(ROUTINGTABLES_DIR)), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(ROUTINGTABLES_DIR), xmlFilter, true); - writeDir(app.getFile(ApplicationPackage.MODELS_GENERATED_REPLICATED_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.MODELS_GENERATED_REPLICATED_DIR), + writeDir(app.getFile(MODELS_GENERATED_REPLICATED_DIR), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(MODELS_GENERATED_REPLICATED_DIR), true); - writeDir(app.getFile(ApplicationPackage.SECURITY_DIR), - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH).append(ApplicationPackage.SECURITY_DIR), + writeDir(app.getFile(SECURITY_DIR), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(SECURITY_DIR), true); } @@ -233,10 +205,10 @@ public class ZooKeeperClient { ApplicationFile dir = applicationPackage.getFile(Path.fromString(userInclude)); final List<ApplicationFile> files = dir.listFiles(); if (files == null || files.isEmpty()) { - configCurator.createNode(getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + userInclude).getAbsolute()); + configCurator.createNode(getZooKeeperAppPath(USERAPP_ZK_SUBPATH + "/" + userInclude).getAbsolute()); } writeDir(dir, - getZooKeeperAppPath(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + userInclude), + getZooKeeperAppPath(USERAPP_ZK_SUBPATH + "/" + userInclude), xmlFilter, true); } } @@ -250,8 +222,8 @@ public class ZooKeeperClient { for (Map.Entry<ConfigDefinitionKey, UnparsedConfigDefinition> entry : configDefs.entrySet()) { ConfigDefinitionKey key = entry.getKey(); String contents = entry.getValue().getUnparsedContent(); - writeConfigDefinition(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); - writeConfigDefinition(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); + writeConfigDefinition(key.getName(), key.getNamespace(), getZooKeeperAppPath(USER_DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); + writeConfigDefinition(key.getName(), key.getNamespace(), getZooKeeperAppPath(DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); } logger.log(Level.FINE, configDefs.size() + " user config definitions"); } @@ -262,8 +234,7 @@ public class ZooKeeperClient { private void write(Version vespaVersion, FileRegistry fileRegistry) { String exportedRegistry = PreGeneratedFileRegistry.exportRegistry(fileRegistry); - - configCurator.putData(getZooKeeperAppPath(null).append(ZKApplicationPackage.fileRegistryNode).getAbsolute(), + configCurator.putData(getZooKeeperAppPath(ZKApplicationPackage.fileRegistryNode).getAbsolute(), vespaVersion.toFullString(), exportedRegistry); } @@ -275,40 +246,42 @@ public class ZooKeeperClient { * @param metaData The application metadata. */ private void write(ApplicationMetaData metaData) { - configCurator.putData(getZooKeeperAppPath(ConfigCurator.META_ZK_PATH).getAbsolute(), metaData.asJsonBytes()); + configCurator.putData(getZooKeeperAppPath(META_ZK_PATH).getAbsolute(), metaData.asJsonBytes()); } void cleanupZooKeeper() { try { - for (String subPath : Arrays.asList( - ConfigCurator.DEFCONFIGS_ZK_SUBPATH, - ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH, - ConfigCurator.USERAPP_ZK_SUBPATH)) { - configCurator.deleteRecurse(getZooKeeperAppPath(null).append(subPath).getAbsolute()); - } + List.of(DEFCONFIGS_ZK_SUBPATH, USER_DEFCONFIGS_ZK_SUBPATH, USERAPP_ZK_SUBPATH) + .forEach(path -> configCurator.deleteRecurse(getZooKeeperAppPath(path).getAbsolute())); } catch (Exception e) { - logger.log(Level.WARNING, "Could not clean up in zookeeper"); + logger.log(Level.WARNING, "Could not clean up in zookeeper: " + Exceptions.toMessageString(e)); //Might be called in an exception handler before re-throw, so do not throw here. } } /** - * Gets a full ZK app path based on id set in Admin object + * Gets a full ZK application path * + * @return a String with the full ZK application path + */ + private Path getZooKeeperAppPath() { + return getZooKeeperAppPath(null); + } + + /** + * Gets a full ZK application path * * @param trailingPath trailing part of path to be appended to ZK app path * @return a String with the full ZK application path including trailing path, if set */ private Path getZooKeeperAppPath(String trailingPath) { - if (trailingPath != null) { - return rootPath.append(trailingPath); - } else { - return rootPath; - } + if (trailingPath == null) return sessionPath; + + return sessionPath.append(trailingPath); } public void write(AllocatedHosts hosts) throws IOException { - configCurator.putData(rootPath.append(ZKApplicationPackage.allocatedHostsNode).getAbsolute(), + configCurator.putData(sessionPath.append(ZKApplicationPackage.allocatedHostsNode).getAbsolute(), AllocatedHostsSerializer.toJson(hosts)); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java index e24d24f0c3f..12aa5b7cc35 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployer.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.deploy; import com.yahoo.config.application.api.ApplicationPackage; @@ -11,7 +11,6 @@ import java.util.Map; /** * Interface for initializing zookeeper and deploying an application package to zookeeper. - * Initialize must be called before each deploy. * * @author Ulf Lilleengen */ @@ -24,16 +23,16 @@ public class ZooKeeperDeployer { } /** - * Deploys an application package to zookeeper. initialize() must be called before calling this method. + * Deploys an application package to zookeeper * * @param applicationPackage the application package to persist. * @param fileRegistryMap the file registries to persist. - * @param allocatedHosts the provisioning info to persist. + * @param allocatedHosts the allocated hosts to persist. * @throws IOException if deploying fails */ public void deploy(ApplicationPackage applicationPackage, Map<Version, FileRegistry> fileRegistryMap, AllocatedHosts allocatedHosts) throws IOException { - zooKeeperClient.setupZooKeeper(); + zooKeeperClient.initialize(); zooKeeperClient.write(applicationPackage); zooKeeperClient.write(fileRegistryMap); zooKeeperClient.write(allocatedHosts); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java index 8bc792a179d..5b520b10fcf 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationApiHandler.java @@ -16,6 +16,8 @@ import com.yahoo.vespa.config.server.http.SessionHandler; import com.yahoo.vespa.config.server.http.Utils; import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.model.content.Content; +import org.apache.hc.core5.http.ContentType; import org.eclipse.jetty.http.MultiPartFormInputStream; import javax.servlet.http.Part; @@ -70,7 +72,8 @@ public class ApplicationApiHandler extends SessionHandler { PrepareParams prepareParams; CompressedApplicationInputStream compressedStream; boolean multipartRequest = Optional.ofNullable(request.getHeader(HttpHeaders.Names.CONTENT_TYPE)) - .map(val -> val.equalsIgnoreCase(MULTIPART_FORM_DATA)) + .map(ContentType::parse) + .map(contentType -> contentType.getMimeType().equalsIgnoreCase(MULTIPART_FORM_DATA)) .orElse(false); if(multipartRequest) { try { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java index a3e82c51dfa..61f099fb8ea 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.TenantName; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.jdisc.HeaderFields; import com.yahoo.jdisc.application.UriPattern; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger; @@ -16,6 +17,8 @@ import com.yahoo.vespa.config.server.TimeoutBudget; import com.yahoo.vespa.config.server.http.BadRequestException; import com.yahoo.vespa.config.server.http.SessionHandler; import com.yahoo.vespa.config.server.http.Utils; +import com.yahoo.vespa.model.content.Content; +import org.apache.hc.core5.http.ContentType; import java.net.URI; import java.time.Duration; @@ -93,9 +96,12 @@ public class SessionCreateHandler extends SessionHandler { String header = request.getHeader(ApplicationApiHandler.contentTypeHeader); if (header == null) { throw new BadRequestException("Request contains no " + ApplicationApiHandler.contentTypeHeader + " header"); - } else if (!supportedContentTypes.contains(header)) { - throw new BadRequestException("Request contains invalid " + ApplicationApiHandler.contentTypeHeader + " header, only '[" - + String.join(", ", supportedContentTypes) + "' are supported"); + } else { + ContentType contentType = ContentType.parse(header); + if (!supportedContentTypes.contains(contentType.getMimeType())) { + throw new BadRequestException("Request contains invalid " + ApplicationApiHandler.contentTypeHeader + " header (" + contentType.getMimeType() + "), only '[" + + String.join(", ", supportedContentTypes) + "]' are supported"); + } } } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java index 5c0207878f1..f878831b8c5 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java @@ -76,8 +76,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { ConfigserverConfig configserverConfig, Zone zone, ModelFactoryRegistry modelFactoryRegistry, - ConfigDefinitionRepo configDefinitionRepo, - TenantListener tenantListener) { + ConfigDefinitionRepo configDefinitionRepo) { super(modelFactoryRegistry, configserverConfig, zone, diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index 30cdc0f6e8a..50b9ac55bda 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -27,7 +27,6 @@ import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.lang.SettableOptional; import com.yahoo.path.Path; import com.yahoo.vespa.config.server.TimeoutBudget; -import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; @@ -44,7 +43,6 @@ import com.yahoo.vespa.config.server.tenant.ApplicationRolesStore; import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache; import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore; import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever; -import com.yahoo.vespa.config.server.tenant.SecretStoreExternalIdRetriever; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagSource; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index cb46d65c4c5..7d74b53fdff 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -121,7 +121,6 @@ public class SessionRepository { private final Zone zone; private final ModelFactoryRegistry modelFactoryRegistry; private final ConfigDefinitionRepo configDefinitionRepo; - private final TenantListener tenantListener; public SessionRepository(TenantName tenantName, TenantApplications applicationRepo, @@ -139,8 +138,7 @@ public class SessionRepository { Zone zone, Clock clock, ModelFactoryRegistry modelFactoryRegistry, - ConfigDefinitionRepo configDefinitionRepo, - TenantListener tenantListener) { + ConfigDefinitionRepo configDefinitionRepo) { this.tenantName = tenantName; this.configCurator = configCurator; sessionCounter = new SessionCounter(configCurator, tenantName); @@ -163,7 +161,6 @@ public class SessionRepository { this.zone = zone; this.modelFactoryRegistry = modelFactoryRegistry; this.configDefinitionRepo = configDefinitionRepo; - this.tenantListener = tenantListener; loadSessions(); // Needs to be done before creating cache below this.directoryCache = curator.createDirectoryCache(sessionsPath.getAbsolute(), false, false, zkCacheExecutor); @@ -502,8 +499,7 @@ public class SessionRepository { configserverConfig, zone, modelFactoryRegistry, - configDefinitionRepo, - tenantListener); + configDefinitionRepo); // Read hosts allocated on the config server instance which created this SettableOptional<AllocatedHosts> allocatedHosts = new SettableOptional<>(applicationPackage.getAllocatedHosts()); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java index 1c1d2534341..21db290d5e8 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java @@ -357,8 +357,7 @@ public class TenantRepository { zone, clock, modelFactoryRegistry, - configDefinitionRepo, - tenantListener); + configDefinitionRepo); log.log(Level.INFO, "Adding tenant '" + tenantName + "'" + ", created " + created + ". Bootstrapping in " + Duration.between(start, Instant.now())); Tenant tenant = new Tenant(tenantName, sessionRepository, applicationRepo, created); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java index 665f37759b4..f01bf1fe6ef 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackage.java @@ -45,7 +45,6 @@ public class ZKApplicationPackage implements ApplicationPackage { private final Map<Version, PreGeneratedFileRegistry> fileRegistryMap = new HashMap<>(); private final Optional<AllocatedHosts> allocatedHosts; - private static final Version legacyVersion = new Version(0, 0, 0); public static final String fileRegistryNode = "fileregistry"; public static final String allocatedHostsNode = "allocatedHosts"; @@ -60,7 +59,7 @@ public class ZKApplicationPackage implements ApplicationPackage { } private Optional<AllocatedHosts> importAllocatedHosts() { - if ( ! zkApplication.exists(ZKApplicationPackage.allocatedHostsNode)) return Optional.empty(); + if ( ! zkApplication.exists(allocatedHostsNode)) return Optional.empty(); return Optional.of(readAllocatedHosts()); } @@ -71,21 +70,18 @@ public class ZKApplicationPackage implements ApplicationPackage { */ private AllocatedHosts readAllocatedHosts() { try { - return AllocatedHostsSerializer.fromJson(zkApplication.getBytes(ZKApplicationPackage.allocatedHostsNode)); + return AllocatedHostsSerializer.fromJson(zkApplication.getBytes(allocatedHostsNode)); } catch (Exception e) { throw new RuntimeException("Unable to read allocated hosts", e); } } private void importFileRegistries() { - List<String> fileRegistryNodes = zkApplication.getChildren(ZKApplicationPackage.fileRegistryNode); - if (fileRegistryNodes.isEmpty()) { - fileRegistryMap.put(legacyVersion, importFileRegistry(ZKApplicationPackage.fileRegistryNode)); - } else { - fileRegistryNodes.forEach(version -> - fileRegistryMap.put(Version.fromString(version), - importFileRegistry(Joiner.on("/").join(ZKApplicationPackage.fileRegistryNode, version)))); - } + List<String> perVersionFileRegistryNodes = zkApplication.getChildren(fileRegistryNode); + perVersionFileRegistryNodes + .forEach(version -> + fileRegistryMap.put(Version.fromString(version), + importFileRegistry(Joiner.on("/").join(fileRegistryNode, version)))); } private PreGeneratedFileRegistry importFileRegistry(String fileRegistryNode) { @@ -139,11 +135,11 @@ public class ZKApplicationPackage implements ApplicationPackage { public List<NamedReader> searchDefinitionContents() { List<NamedReader> schemas = new ArrayList<>(); for (String sd : zkApplication.getChildren(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SEARCH_DEFINITIONS_DIR)) { - if (sd.endsWith(ApplicationPackage.SD_NAME_SUFFIX)) + if (sd.endsWith(SD_NAME_SUFFIX)) schemas.add(new NamedReader(sd, new StringReader(zkApplication.getData(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SEARCH_DEFINITIONS_DIR, sd)))); } for (String sd : zkApplication.getChildren(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SCHEMAS_DIR)) { - if (sd.endsWith(ApplicationPackage.SD_NAME_SUFFIX)) + if (sd.endsWith(SD_NAME_SUFFIX)) schemas.add(new NamedReader(sd, new StringReader(zkApplication.getData(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SCHEMAS_DIR, sd)))); } return schemas; @@ -251,7 +247,7 @@ public class ZKApplicationPackage implements ApplicationPackage { List<ComponentInfo> components = new ArrayList<>(); PreGeneratedFileRegistry fileRegistry = getPreGeneratedFileRegistry(vespaVersion).get(); for (String path : fileRegistry.getPaths()) { - if (path.startsWith(ApplicationPackage.COMPONENT_DIR + File.separator) && path.endsWith(".jar")) { + if (path.startsWith(COMPONENT_DIR + File.separator) && path.endsWith(".jar")) { ComponentInfo component = new ComponentInfo(path); components.add(component); } @@ -266,9 +262,7 @@ public class ZKApplicationPackage implements ApplicationPackage { @Override public Reader getRankingExpression(String name) { Optional<Reader> reader = zkApplication.getOptionalDataReader(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SCHEMAS_DIR, name); - if (reader.isPresent()) - return reader.get(); - return zkApplication.getDataReader(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SEARCH_DEFINITIONS_DIR, name); + return reader.orElseGet(() -> zkApplication.getDataReader(ConfigCurator.USERAPP_ZK_SUBPATH + "/" + SEARCH_DEFINITIONS_DIR, name)); } @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java index deff0aba376..7d14b1996b0 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java @@ -63,7 +63,7 @@ public class ZooKeeperClientTest { 2L)); Map<Version, FileRegistry> fileRegistries = createFileRegistries(); app.writeMetaData(); - zkc.setupZooKeeper(); + zkc.initialize(); zkc.write(app); zkc.write(fileRegistries); } @@ -85,7 +85,7 @@ public class ZooKeeperClientTest { BaseDeployLogger logger = new BaseDeployLogger(); long generation = 1L; ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, Path.fromString("/1")); - zooKeeperClient.setupZooKeeper(); + zooKeeperClient.initialize(); String appPath = "/"; assertThat(zk.getChildren(appPath).size(), is(1)); assertTrue(zk.exists("/" + generation)); @@ -157,7 +157,7 @@ public class ZooKeeperClientTest { BaseDeployLogger logger = new BaseDeployLogger(); Path app = Path.fromString("/1"); ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, app); - zooKeeperClient.setupZooKeeper(); + zooKeeperClient.initialize(); HostSpec host1 = new HostSpec("host1.yahoo.com", Collections.emptyList(), Optional.empty()); HostSpec host2 = new HostSpec("host2.yahoo.com", Collections.emptyList(), Optional.empty()); ImmutableSet<HostSpec> hosts = ImmutableSet.of(host1, host2); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java index 83d6ac5b288..5d7322070e7 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java @@ -16,19 +16,24 @@ import com.yahoo.vespa.config.server.http.SessionHandlerTest; import com.yahoo.vespa.config.server.session.Session; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.tenant.TestTenantRepository; +import org.apache.hc.core5.http.ContentType; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; @@ -43,6 +48,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author hmusum @@ -171,6 +177,22 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod/region/baz/instance"); } + @Test + public void require_that_content_type_is_parsed_correctly() throws FileNotFoundException { + HttpRequest request = post(new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)), + Map.of("Content-Type", "multipart/form-data; charset=ISO-8859-1; boundary=g5gJAzUWl_t6"), + Collections.emptyMap()); + + // Valid header should validate ok + SessionCreateHandler.validateDataAndHeader(request, List.of(ContentType.MULTIPART_FORM_DATA.getMimeType())); + + // Accepting only application/json should fail: + try { + SessionCreateHandler.validateDataAndHeader(request, List.of(ContentType.APPLICATION_JSON.getMimeType())); + fail("Request contained invalid content type, but validated ok"); + } catch (Exception expected) {} + } + private SessionCreateHandler createHandler() { return new SessionCreateHandler(SessionCreateHandler.testOnlyContext(), applicationRepository, @@ -178,7 +200,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { } private HttpRequest post() throws FileNotFoundException { - return post(null, postHeaders, new HashMap<>()); + return post((InputStream) null, postHeaders, new HashMap<>()); } private HttpRequest post(File file) throws FileNotFoundException { @@ -186,10 +208,12 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { } private HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException { + return post(file == null ? null : new FileInputStream(file), headers, parameters); + } + + private HttpRequest post(InputStream data, Map <String, String > headers, Map < String, String > parameters) throws FileNotFoundException { HttpRequest request = HttpRequest.createTestRequest("http://" + hostname + ":" + port + "/application/v2/tenant/" + tenant + "/session", - POST, - file == null ? null : new FileInputStream(file), - parameters); + POST, data, parameters); for (Map.Entry<String, String> entry : headers.entrySet()) { request.getJDiscRequest().headers().put(entry.getKey(), entry.getValue()); } @@ -197,6 +221,6 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { } private HttpRequest post(Map<String, String> parameters) throws FileNotFoundException { - return post(null, new HashMap<>(), parameters); + return post((InputStream) null, new HashMap<>(), parameters); } } 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 11898381f0a..08b89094486 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,10 @@ import com.yahoo.container.logging.RequestLog; import com.yahoo.container.logging.RequestLogEntry; import com.yahoo.jdisc.http.ServerConfig; import com.yahoo.jdisc.http.servlet.ServletRequest; +import org.eclipse.jetty.http2.HTTP2Stream; +import org.eclipse.jetty.http2.server.HttpTransportOverHTTP2; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.component.AbstractLifeCycle; @@ -117,6 +121,7 @@ class AccessLogRequestLog extends AbstractLifeCycle implements org.eclipse.jetty addNonNullValue(builder, accessLogEntry.getHitCounts(), RequestLogEntry.Builder::hitCounts); addNonNullValue(builder, accessLogEntry.getTrace(), RequestLogEntry.Builder::traceNode); } + http2StreamId(request).ifPresent(streamId -> builder.addExtraAttribute("http2-stream-id", Integer.toString(streamId))); requestLog.log(builder.build()); } catch (Exception e) { @@ -158,6 +163,15 @@ class AccessLogRequestLog extends AbstractLifeCycle implements org.eclipse.jetty } } + private static OptionalInt http2StreamId(Request request) { + HttpChannel httpChannel = request.getHttpChannel(); + if (httpChannel == null) return OptionalInt.empty(); + HttpTransport transport = httpChannel.getHttpTransport(); + if (!(transport instanceof HttpTransportOverHTTP2)) return OptionalInt.empty(); + HTTP2Stream stream = (HTTP2Stream) ((HttpTransportOverHTTP2) transport).getStream(); + return OptionalInt.of(stream.getId()); + } + private static <T> void addNonNullValue( RequestLogEntry.Builder builder, T value, BiConsumer<RequestLogEntry.Builder, T> setter) { if (value != null) { 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 cb1e366f843..09b883a620e 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 @@ -127,4 +127,4 @@ maxRequestsPerConnection int default=0 maxConnectionLife double default=0.0 # Enable HTTP/2 (in addition to HTTP/1.1 using ALPN) -http2Enabled bool default=false +http2Enabled bool default=true 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 5056cf91d79..0dec711f4c0 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 @@ -35,8 +35,8 @@ import com.yahoo.security.SslContextBuilder; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; import com.yahoo.security.tls.TlsContext; -import org.apache.hc.client5.http.async.methods.SimpleHttpRequests; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.entity.mime.FormBodyPart; import org.apache.hc.client5.http.entity.mime.FormBodyPartBuilder; import org.apache.hc.client5.http.entity.mime.StringBody; @@ -523,9 +523,9 @@ public class HttpServerTest { MetricConsumerMock metricConsumer = new MetricConsumerMock(); InMemoryConnectionLog connectionLog = new InMemoryConnectionLog(); JettyTestDriver driver = createSslTestDriver(certificateFile, privateKeyFile, metricConsumer, connectionLog); - try (CloseableHttpAsyncClient client = createHttp2Client(certificateFile, privateKeyFile)) { + try (CloseableHttpAsyncClient client = createHttp2Client(driver)) { String uri = "https://localhost:" + driver.server().getListenPort() + "/status.html"; - SimpleHttpResponse response = client.execute(SimpleHttpRequests.get(uri), null).get(); + SimpleHttpResponse response = client.execute(SimpleRequestBuilder.get(uri).build(), null).get(); assertNull(response.getBodyText()); assertEquals(OK, response.getCode()); } @@ -967,8 +967,7 @@ public class HttpServerTest { return client; } - private static CloseableHttpAsyncClient createHttp2Client(Path certificateFile, Path privateKeyFile) { - JettyTestDriver driver = JettyTestDriver.newInstanceWithSsl(new EchoRequestHandler(), certificateFile, privateKeyFile, TlsClientAuth.WANT); + private static CloseableHttpAsyncClient createHttp2Client(JettyTestDriver driver) { TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() .setSslContext(driver.sslContext()) .build(); diff --git a/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java b/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java index f6bf91f5f85..8d0e4944ab8 100644 --- a/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java +++ b/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java @@ -338,9 +338,8 @@ public class FederationSearcher extends ForkingSearcher { private List<String> allSourceRefDescriptions() { List<String> descriptions = new ArrayList<>(); - for (com.yahoo.search.federation.sourceref.Target target : searchChainResolver.allTopLevelTargets()) { + for (com.yahoo.search.federation.sourceref.Target target : searchChainResolver.allTopLevelTargets()) descriptions.add(target.searchRefDescription()); - } return descriptions; } diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java index 6cb8d2ef174..59b4e521a56 100644 --- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java +++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Objects; /** - * Specifices which search chain should be run and how it should be run. + * Specifies which search chain should be run and how it should be run. * This is a value object. * * @author Tony Vaagenes diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java index 36f6fe424a0..6626c1b3cc4 100644 --- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java +++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java @@ -92,7 +92,6 @@ public class SearchChainResolver { public Builder addSourceForProvider(ComponentId sourceId, ComponentId providerId, ComponentId searchChainId, boolean isDefaultProviderForSource, FederationOptions federationOptions, List<String> documentTypes) { - SearchChainInvocationSpec searchChainInvocationSpec = new SearchChainInvocationSpec(searchChainId, sourceId, providerId, federationOptions, documentTypes); @@ -133,7 +132,6 @@ public class SearchChainResolver { this.defaultTargets = Collections.unmodifiableSortedSet(defaultTargets); } - public SearchChainInvocationSpec resolve(ComponentSpecification sourceRef, Properties sourceToProviderMap) throws UnresolvedSearchChainException { diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java index 9c7e1024518..4613c73c4b4 100644 --- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java +++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java @@ -5,8 +5,6 @@ import com.yahoo.component.ComponentId; import com.yahoo.processing.request.Properties; /** - * TODO: What is this? - * * @author Tony Vaagenes */ public class SingleTarget extends Target { diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java index f23e24525bb..1b11e588f11 100644 --- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java +++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java @@ -6,8 +6,6 @@ import com.yahoo.component.ComponentId; import com.yahoo.processing.request.Properties; /** - * TODO: What's this? - * * @author Tony Vaagenes */ public abstract class Target extends AbstractComponent { diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java b/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java index c264425cb9c..2f680a8f3bd 100644 --- a/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java +++ b/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java @@ -60,8 +60,9 @@ public class VespaSearchers { private static FederationSearcherModel federationSearcherModel() { return new FederationSearcherModel(new ComponentSpecification("federation"), - Dependencies.emptyDependencies(), - Collections.emptyList(), true); + Dependencies.emptyDependencies(), + Collections.emptyList(), + true); } private static boolean allAdded(Collection<ChainedComponentModel> searcherModels, Set<ComponentId> componentIds) { diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java b/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java index 2778f7e97db..01dccee5c7f 100644 --- a/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java +++ b/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java @@ -5,7 +5,6 @@ import java.util.List; import com.google.common.collect.ImmutableList; import com.yahoo.container.bundle.BundleInstantiationSpecification; -import net.jcip.annotations.Immutable; import com.yahoo.component.ComponentSpecification; import com.yahoo.component.chain.dependencies.Dependencies; @@ -17,23 +16,8 @@ import com.yahoo.search.federation.FederationSearcher; * * @author Tony Vaagenes */ -@Immutable public class FederationSearcherModel extends ChainedComponentModel { - /** - * Specifies one or more search chains that can be addressed - * as a single source. - */ - public static class TargetSpec { - public final ComponentSpecification sourceSpec; - public final FederationOptions federationOptions; - - public TargetSpec(ComponentSpecification sourceSpec, FederationOptions federationOptions) { - this.sourceSpec = sourceSpec; - this.federationOptions = federationOptions; - } - } - private static final ComponentSpecification federationSearcherComponentSpecification = new ComponentSpecification(FederationSearcher.class.getName()); @@ -48,4 +32,16 @@ public class FederationSearcherModel extends ChainedComponentModel { this.targets = ImmutableList.copyOf(targets); } + /** Specifies one or more search chains that can be addressed as a single source. */ + public static class TargetSpec { + + public final ComponentSpecification sourceSpec; + public final FederationOptions federationOptions; + + public TargetSpec(ComponentSpecification sourceSpec, FederationOptions federationOptions) { + this.sourceSpec = sourceSpec; + this.federationOptions = federationOptions; + } + } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java index 5216c1421fa..63701ca9547 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMetadata.java @@ -18,18 +18,18 @@ public class EndpointCertificateMetadata { private final String certName; private final int version; private final long lastRequested; - private final String request_id; + private final String requestId; private final List<String> requestedDnsSans; private final String issuer; private final Optional<Long> expiry; private final Optional<Long> lastRefreshed; - public EndpointCertificateMetadata(String keyName, String certName, int version, long lastRequested, String request_id, List<String> requestedDnsSans, String issuer, Optional<Long> expiry, Optional<Long> lastRefreshed) { + public EndpointCertificateMetadata(String keyName, String certName, int version, long lastRequested, String requestId, List<String> requestedDnsSans, String issuer, Optional<Long> expiry, Optional<Long> lastRefreshed) { this.keyName = keyName; this.certName = certName; this.version = version; this.lastRequested = lastRequested; - this.request_id = request_id; + this.requestId = requestId; this.requestedDnsSans = requestedDnsSans; this.issuer = issuer; this.expiry = expiry; @@ -52,8 +52,8 @@ public class EndpointCertificateMetadata { return lastRequested; } - public String request_id() { - return request_id; + public String requestId() { + return requestId; } public List<String> requestedDnsSans() { @@ -78,7 +78,7 @@ public class EndpointCertificateMetadata { this.certName, version, this.lastRequested, - this.request_id, + this.requestId, this.requestedDnsSans, this.issuer, this.expiry, @@ -91,7 +91,7 @@ public class EndpointCertificateMetadata { this.certName, this.version, lastRequested, - this.request_id, + this.requestId, this.requestedDnsSans, this.issuer, this.expiry, @@ -104,7 +104,7 @@ public class EndpointCertificateMetadata { this.certName, this.version, this.lastRequested, - this.request_id, + this.requestId, this.requestedDnsSans, this.issuer, this.expiry, @@ -127,16 +127,16 @@ public class EndpointCertificateMetadata { @Override public String toString() { return "EndpointCertificateMetadata{" + - "keyName='" + keyName + '\'' + - ", certName='" + certName + '\'' + - ", version=" + version + - ", lastRequested=" + lastRequested + - ", request_id=" + request_id + - ", requestedDnsSans=" + requestedDnsSans + - ", issuer=" + issuer + - ", expiry=" + expiry + - ", lastRefreshed=" + lastRefreshed + - '}'; + "keyName='" + keyName + '\'' + + ", certName='" + certName + '\'' + + ", version=" + version + + ", lastRequested=" + lastRequested + + ", requestId=" + requestId + + ", requestedDnsSans=" + requestedDnsSans + + ", issuer=" + issuer + + ", expiry=" + expiry + + ", lastRefreshed=" + lastRefreshed + + '}'; } @Override @@ -146,17 +146,18 @@ public class EndpointCertificateMetadata { EndpointCertificateMetadata that = (EndpointCertificateMetadata) o; return version == that.version && lastRequested == that.lastRequested && - keyName.equals(that.keyName) && - certName.equals(that.certName) && - request_id.equals(that.request_id) && - requestedDnsSans.equals(that.requestedDnsSans) && - issuer.equals(that.issuer) && - expiry.equals(that.expiry) && - lastRefreshed.equals(that.lastRefreshed); + keyName.equals(that.keyName) && + certName.equals(that.certName) && + requestId.equals(that.requestId) && + requestedDnsSans.equals(that.requestedDnsSans) && + issuer.equals(that.issuer) && + expiry.equals(that.expiry) && + lastRefreshed.equals(that.lastRefreshed); } @Override public int hashCode() { - return Objects.hash(keyName, certName, version, lastRequested, request_id, requestedDnsSans, issuer, expiry, lastRefreshed); + return Objects.hash(keyName, certName, version, lastRequested, requestId, requestedDnsSans, issuer, expiry, lastRefreshed); } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMock.java index b5ee78251f0..07da6969b64 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateMock.java @@ -34,11 +34,12 @@ public class EndpointCertificateMock implements EndpointCertificateProvider { @Override public List<EndpointCertificateMetadata> listCertificates() { - return Collections.emptyList(); + return List.of(); } @Override public void deleteCertificate(ApplicationId applicationId, EndpointCertificateMetadata endpointCertificateMetadata) { dnsNames.remove(applicationId); } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java index ea92f84f72f..9ce4fccc375 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java @@ -16,6 +16,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +/** + * @author andreer + */ public class EndpointCertificateValidatorImpl implements EndpointCertificateValidator { private final SecretStore secretStore; private final Clock clock; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java index 7e89a3becd5..780701b3b77 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java @@ -5,7 +5,11 @@ import com.yahoo.config.provision.zone.ZoneId; import java.util.List; +/** + * @author andreer + */ public class EndpointCertificateValidatorMock implements EndpointCertificateValidator { + @Override public void validate( EndpointCertificateMetadata endpointCertificateMetadata, @@ -14,4 +18,5 @@ public class EndpointCertificateValidatorMock implements EndpointCertificateVali List<String> requiredNamesForZone) { // Mock does no validation - for unit tests only! } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Node.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Node.java index 0f9e12d8cf2..5f46b949844 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Node.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Node.java @@ -548,6 +548,11 @@ public class Node { return this; } + public Builder reports(Map<String, JsonNode> reports) { + this.reports = reports; + return this; + } + public Node build() { return new Node(hostname, parentHostname, state, type, resources, owner, currentVersion, wantedVersion, currentOsVersion, wantedOsVersion, currentFirmwareCheck, wantedFirmwareCheck, serviceState, diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VCMRReport.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VCMRReport.java new file mode 100644 index 00000000000..a3c0af95053 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VCMRReport.java @@ -0,0 +1,148 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.integration.vcmr; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; + +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author olaa + * + * Node repository report containing list of upcoming VCMRs impacting a node + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class VCMRReport { + + private static final String REPORT_ID = "vcmr"; + private static final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()); + + @JsonProperty("upcoming") + private Set<VCMR> vcmrs; + + public VCMRReport() { + this(new HashSet<>()); + } + + public VCMRReport(Set<VCMR> vcmrs) { + this.vcmrs = vcmrs; + } + + public Set<VCMR> getVcmrs() { + return vcmrs; + } + + /** + * @return true if list of VCMRs is changed + */ + public boolean addVcmr(String id, ZonedDateTime plannedStartTime, ZonedDateTime plannedEndtime) { + var vcmr = new VCMR(id, plannedStartTime, plannedEndtime); + if (vcmrs.contains(vcmr)) + return false; + + // Remove to catch any changes in start/end time + removeVcmr(id); + return vcmrs.add(vcmr); + } + + public boolean removeVcmr(String id) { + return vcmrs.removeIf(vcmr -> id.equals(vcmr.getId())); + } + + public static String getReportId() { + return REPORT_ID; + } + + /** + * Serialization functions - mapped to {@link Node#reports()} + */ + public static VCMRReport fromReports(Map<String, JsonNode> reports) { + var serialized = reports.get(REPORT_ID); + if (serialized == null) + return new VCMRReport(); + + return uncheck(() -> objectMapper.treeToValue(serialized, VCMRReport.class)); + } + + /** + * Set report to 'null' if list is empty - clearing the report + * See NodePatcher in node-repository + */ + public Map<String, JsonNode> toNodeReports() { + Map<String, JsonNode> reports = new HashMap<>(); + JsonNode jsonNode = vcmrs.isEmpty() ? + null : uncheck(() -> objectMapper.valueToTree(this)); + reports.put(REPORT_ID, jsonNode); + return reports; + } + + @Override + public String toString() { + return "VCMRReport{" + vcmrs + "}"; + } + + public static class VCMR { + + private String id; + private ZonedDateTime plannedStartTime; + private ZonedDateTime plannedEndTime; + + VCMR(@JsonProperty("id") String id, + @JsonProperty("plannedStartTime") ZonedDateTime plannedStartTime, + @JsonProperty("plannedEndTime") ZonedDateTime plannedEndTime) { + this.id = id; + this.plannedStartTime = plannedStartTime; + this.plannedEndTime = plannedEndTime; + } + + public String getId() { + return id; + } + + public ZonedDateTime getPlannedStartTime() { + return plannedStartTime; + } + + public ZonedDateTime getPlannedEndTime() { + return plannedEndTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VCMR vcmr = (VCMR) o; + return Objects.equals(id, vcmr.id) && + Objects.equals(plannedStartTime, vcmr.plannedStartTime) && + Objects.equals(plannedEndTime, vcmr.plannedEndTime); + } + + @Override + public int hashCode() { + return Objects.hash(id, plannedStartTime, plannedEndTime); + } + + @Override + public String toString() { + return "VCMR{" + + "id='" + id + '\'' + + ", plannedStartTime=" + plannedStartTime + + ", plannedEndTime=" + plannedEndTime + + '}'; + } + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index e6c96134ca7..0f9188d1f65 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -52,7 +52,7 @@ import com.yahoo.vespa.hosted.controller.application.QuotaUsage; import com.yahoo.vespa.hosted.controller.application.SystemApplication; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade; -import com.yahoo.vespa.hosted.controller.certificate.EndpointCertificateManager; +import com.yahoo.vespa.hosted.controller.certificate.EndpointCertificates; import com.yahoo.vespa.hosted.controller.concurrent.Once; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger; import com.yahoo.vespa.hosted.controller.deployment.JobStatus; @@ -118,7 +118,7 @@ public class ApplicationController { private final Clock clock; private final DeploymentTrigger deploymentTrigger; private final ApplicationPackageValidator applicationPackageValidator; - private final EndpointCertificateManager endpointCertificateManager; + private final EndpointCertificates endpointCertificates; private final StringFlag dockerImageRepoFlag; private final BillingController billingController; @@ -137,12 +137,9 @@ public class ApplicationController { deploymentTrigger = new DeploymentTrigger(controller, clock); applicationPackageValidator = new ApplicationPackageValidator(controller); - endpointCertificateManager = new EndpointCertificateManager( - controller.zoneRegistry(), - curator, - controller.serviceRegistry().endpointCertificateProvider(), - controller.serviceRegistry().endpointCertificateValidator(), - clock); + endpointCertificates = new EndpointCertificates(controller, + controller.serviceRegistry().endpointCertificateProvider(), + controller.serviceRegistry().endpointCertificateValidator()); // Update serialization format of all applications Once.after(Duration.ofMinutes(1), () -> { @@ -382,7 +379,7 @@ public class ApplicationController { && run.testerCertificate().isPresent()) applicationPackage = applicationPackage.withTrustedCertificate(run.testerCertificate().get()); - endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(instance, zone, applicationPackage.deploymentSpec().instance(instance.name())); + endpointCertificateMetadata = endpointCertificates.getMetadata(instance, zone, applicationPackage.deploymentSpec().instance(instance.name())); containerEndpoints = controller.routing().containerEndpointsOf(application.get(), job.application().instance(), zone); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java index 433b2b340d5..abdb394c278 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java @@ -3,12 +3,16 @@ package com.yahoo.vespa.hosted.controller; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; import com.yahoo.component.Version; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.flags.BooleanFlag; @@ -34,6 +38,7 @@ import com.yahoo.vespa.hosted.controller.routing.RoutingId; import com.yahoo.vespa.hosted.controller.routing.RoutingPolicies; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -67,6 +72,7 @@ public class RoutingController { private final RoutingPolicies routingPolicies; private final RotationRepository rotationRepository; private final BooleanFlag hideSharedRoutingEndpoint; + private final BooleanFlag vespaAppDomainInCertificate; public RoutingController(Controller controller, RotationsConfig rotationsConfig) { this.controller = Objects.requireNonNull(controller, "controller must be non-null"); @@ -74,6 +80,7 @@ public class RoutingController { this.rotationRepository = new RotationRepository(rotationsConfig, controller.applications(), controller.curator()); this.hideSharedRoutingEndpoint = Flags.HIDE_SHARED_ROUTING_ENDPOINT.bindTo(controller.flagSource()); + this.vespaAppDomainInCertificate = Flags.VESPA_APP_DOMAIN_IN_CERTIFICATE.bindTo(controller.flagSource()); } public RoutingPolicies policies() { @@ -149,6 +156,39 @@ public class RoutingController { return Collections.unmodifiableMap(endpoints); } + /** Returns certificate DNS names (CN and SAN values) for given deployment */ + public List<String> certificateDnsNames(DeploymentId deployment) { + List<String> endpointDnsNames = new ArrayList<>(); + + // We add first an endpoint name based on a hash of the application ID, + // as the certificate provider requires the first CN to be < 64 characters long. + endpointDnsNames.add(commonNameHashOf(deployment.applicationId(), controller.system())); + + // Add wildcard names for global endpoints when deploying to production + List<Endpoint.EndpointBuilder> builders = new ArrayList<>(); + if (deployment.zoneId().environment().isProduction()) { + builders.add(Endpoint.of(deployment.applicationId()).target(EndpointId.defaultId())); + builders.add(Endpoint.of(deployment.applicationId()).wildcard()); + } + + // Add wildcard names for zone endpoints + builders.add(Endpoint.of(deployment.applicationId()).target(ClusterSpec.Id.from("default"), deployment.zoneId())); + builders.add(Endpoint.of(deployment.applicationId()).wildcard(deployment.zoneId())); + + // Build all endpoints + for (var builder : builders) { + builder = builder.routingMethod(RoutingMethod.exclusive) + .on(Port.tls()); + Endpoint endpoint = builder.in(controller.system()); + endpointDnsNames.add(endpoint.dnsName()); + if (controller.system().isPublic() && vespaAppDomainInCertificate.with(FetchVector.Dimension.APPLICATION_ID, deployment.applicationId().serializedForm()).value()) { + Endpoint legacyEndpoint = builder.legacy().in(controller.system()); + endpointDnsNames.add(legacyEndpoint.dnsName()); + } + } + return Collections.unmodifiableList(endpointDnsNames); + } + /** Change status of all global endpoints for given deployment */ public void setGlobalRotationStatus(DeploymentId deployment, EndpointStatus status) { endpointsOf(deployment.applicationId()).requiresRotation().primary().ifPresent(endpoint -> { @@ -349,6 +389,13 @@ public class RoutingController { .isPresent(); } + /** Create a common name based on a hash of given application. This must be less than 64 characters long. */ + private static String commonNameHashOf(ApplicationId application, SystemName system) { + HashCode sha1 = Hashing.sha1().hashString(application.serializedForm(), StandardCharsets.UTF_8); + String base32 = BaseEncoding.base32().omitPadding().lowerCase().encode(sha1.asBytes()); + return 'v' + base32 + Endpoint.dnsSuffix(system); + } + /** Returns direct routing endpoints if any exist and feature flag is set for given application */ // TODO: Remove this when feature flag is removed, and in-line .direct() filter where relevant public EndpointList directEndpoints(EndpointList endpoints, ApplicationId application) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java deleted file mode 100644 index 6f964999fba..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManager.java +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.certificate; - -import com.google.common.hash.Hashing; -import com.google.common.io.BaseEncoding; -import com.yahoo.config.application.api.DeploymentInstanceSpec; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.zone.RoutingMethod; -import com.yahoo.config.provision.zone.ZoneApi; -import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.vespa.hosted.controller.Instance; -import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata; -import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider; -import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidator; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.application.Endpoint; -import com.yahoo.vespa.hosted.controller.application.EndpointId; -import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; -import org.jetbrains.annotations.NotNull; - -import java.nio.charset.Charset; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -/** - * Looks up stored endpoint certificate metadata, provisions new certificates if none is found, - * re-provisions if zone is not covered, and uses refreshed certificates if a newer version is available. - * <p> - * See also EndpointCertificateMaintainer, which handles refreshes, deletions and triggers deployments - * - * @author andreer - */ -public class EndpointCertificateManager { - - private static final Logger log = Logger.getLogger(EndpointCertificateManager.class.getName()); - - private final ZoneRegistry zoneRegistry; - private final CuratorDb curator; - private final EndpointCertificateProvider endpointCertificateProvider; - private final Clock clock; - private final EndpointCertificateValidator endpointCertificateValidator; - - public EndpointCertificateManager(ZoneRegistry zoneRegistry, - CuratorDb curator, - EndpointCertificateProvider endpointCertificateProvider, - EndpointCertificateValidator endpointCertificateValidator, - Clock clock) { - this.zoneRegistry = zoneRegistry; - this.curator = curator; - this.endpointCertificateProvider = endpointCertificateProvider; - this.clock = clock; - this.endpointCertificateValidator = endpointCertificateValidator; - } - - public Optional<EndpointCertificateMetadata> getEndpointCertificateMetadata(Instance instance, ZoneId zone, Optional<DeploymentInstanceSpec> instanceSpec) { - var t0 = Instant.now(); - Optional<EndpointCertificateMetadata> metadata = getOrProvision(instance, zone, instanceSpec); - metadata.ifPresent(m -> curator.writeEndpointCertificateMetadata(instance.id(), m.withLastRequested(clock.instant().getEpochSecond()))); - Duration duration = Duration.between(t0, Instant.now()); - if (duration.toSeconds() > 30) - log.log(Level.INFO, String.format("Getting endpoint certificate metadata for %s took %d seconds!", instance.id().serializedForm(), duration.toSeconds())); - return metadata; - } - - @NotNull - private Optional<EndpointCertificateMetadata> getOrProvision(Instance instance, ZoneId zone, Optional<DeploymentInstanceSpec> instanceSpec) { - final var currentCertificateMetadata = curator.readEndpointCertificateMetadata(instance.id()); - - if (currentCertificateMetadata.isEmpty()) { - var provisionedCertificateMetadata = provisionEndpointCertificate(instance, Optional.empty(), zone, instanceSpec); - // We do not verify the certificate if one has never existed before - because we do not want to - // wait for it to be available before we deploy. This allows the config server to start - // provisioning nodes ASAP, and the risk is small for a new deployment. - curator.writeEndpointCertificateMetadata(instance.id(), provisionedCertificateMetadata); - return Optional.of(provisionedCertificateMetadata); - } - - // Re-provision certificate if it is missing SANs for the zone we are deploying to - var requiredSansForZone = dnsNamesOf(instance.id(), zone); - if (!currentCertificateMetadata.get().requestedDnsSans().containsAll(requiredSansForZone)) { - var reprovisionedCertificateMetadata = - provisionEndpointCertificate(instance, currentCertificateMetadata, zone, instanceSpec) - .withRequestId(currentCertificateMetadata.get().request_id()); // We're required to keep the original request_id - curator.writeEndpointCertificateMetadata(instance.id(), reprovisionedCertificateMetadata); - // Verification is unlikely to succeed in this case, as certificate must be available first - controller will retry - endpointCertificateValidator.validate(reprovisionedCertificateMetadata, instance.id().serializedForm(), zone, requiredSansForZone); - return Optional.of(reprovisionedCertificateMetadata); - } - - endpointCertificateValidator.validate(currentCertificateMetadata.get(), instance.id().serializedForm(), zone, requiredSansForZone); - return currentCertificateMetadata; - } - - private EndpointCertificateMetadata provisionEndpointCertificate(Instance instance, Optional<EndpointCertificateMetadata> currentMetadata, ZoneId deploymentZone, Optional<DeploymentInstanceSpec> instanceSpec) { - - List<String> currentlyPresentNames = currentMetadata.isPresent() ? - currentMetadata.get().requestedDnsSans() : Collections.emptyList(); - - var requiredZones = new LinkedHashSet<>(Set.of(deploymentZone)); - - var zoneCandidateList = zoneRegistry.zones().controllerUpgraded().zones().stream().map(ZoneApi::getId).collect(Collectors.toList()); - - // If not deploying to a dev or perf zone, require all prod zones in deployment spec + test and staging - if (!deploymentZone.environment().isManuallyDeployed()) { - zoneCandidateList.stream() - .filter(z -> z.environment().isTest() || instanceSpec.isPresent() && instanceSpec.get().deploysTo(z.environment(), z.region())) - .forEach(requiredZones::add); - } - - var requiredNames = requiredZones.stream() - .flatMap(zone -> dnsNamesOf(instance.id(), zone).stream()) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - // Make sure all currently present names will remain present. - // Instead of just adding "currently present names", we regenerate them in case the names for a zone have changed. - zoneCandidateList.stream() - .map(zone -> dnsNamesOf(instance.id(), zone)) - .filter(zoneNames -> zoneNames.stream().anyMatch(currentlyPresentNames::contains)) - .filter(currentlyPresentNames::containsAll) - .forEach(requiredNames::addAll); - - // This check must be relaxed if we ever remove from the set of names generated. - if (!requiredNames.containsAll(currentlyPresentNames)) - throw new RuntimeException("SANs to be requested do not cover all existing names! Missing names: " - + currentlyPresentNames.stream().filter(s -> !requiredNames.contains(s)).collect(Collectors.joining(", "))); - - return endpointCertificateProvider.requestCaSignedCertificate(instance.id(), List.copyOf(requiredNames), currentMetadata); - } - - - private List<String> dnsNamesOf(ApplicationId applicationId, ZoneId zone) { - List<String> endpointDnsNames = new ArrayList<>(); - - // We add first an endpoint name based on a hash of the applicationId, - // as the certificate provider requires the first CN to be < 64 characters long. - endpointDnsNames.add(commonNameHashOf(applicationId, zoneRegistry.system())); - - List<Endpoint.EndpointBuilder> endpoints = new ArrayList<>(); - - if (zone.environment().isProduction()) { - endpoints.add(Endpoint.of(applicationId).target(EndpointId.defaultId())); - endpoints.add(Endpoint.of(applicationId).wildcard()); - } - - endpoints.add(Endpoint.of(applicationId).target(ClusterSpec.Id.from("default"), zone)); - endpoints.add(Endpoint.of(applicationId).wildcard(zone)); - - endpoints.stream() - .map(endpoint -> endpoint.routingMethod(RoutingMethod.exclusive)) - .map(endpoint -> endpoint.on(Endpoint.Port.tls())) - .map(endpointBuilder -> endpointBuilder.in(zoneRegistry.system())) - .map(Endpoint::dnsName).forEach(endpointDnsNames::add); - - return Collections.unmodifiableList(endpointDnsNames); - } - - /** Create a common name based on a hash of the ApplicationId. This should always be less than 64 characters long. */ - @SuppressWarnings("UnstableApiUsage") - private static String commonNameHashOf(ApplicationId application, SystemName system) { - var hashCode = Hashing.sha1().hashString(application.serializedForm(), Charset.defaultCharset()); - var base32encoded = BaseEncoding.base32().omitPadding().lowerCase().encode(hashCode.asBytes()); - return 'v' + base32encoded + Endpoint.dnsSuffix(system); - } -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java new file mode 100644 index 00000000000..0391cb05bb7 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java @@ -0,0 +1,123 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.certificate; + +import com.yahoo.config.application.api.DeploymentInstanceSpec; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.Instance; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata; +import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider; +import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidator; +import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Looks up stored endpoint certificate metadata, provisions new certificates if none is found, + * re-provisions if zone is not covered, and uses refreshed certificates if a newer version is available. + * + * See also {@link com.yahoo.vespa.hosted.controller.maintenance.EndpointCertificateMaintainer}, which handles + * refreshes, deletions and triggers deployments. + * + * @author andreer + */ +public class EndpointCertificates { + + private static final Logger log = Logger.getLogger(EndpointCertificates.class.getName()); + + private final Controller controller; + private final CuratorDb curator; + private final Clock clock; + private final EndpointCertificateProvider certificateProvider; + private final EndpointCertificateValidator certificateValidator; + + public EndpointCertificates(Controller controller, EndpointCertificateProvider certificateProvider, + EndpointCertificateValidator certificateValidator) { + this.controller = controller; + this.curator = controller.curator(); + this.clock = controller.clock(); + this.certificateProvider = certificateProvider; + this.certificateValidator = certificateValidator; + } + + /** Returns certificate metadata for endpoints of given instance and zone */ + public Optional<EndpointCertificateMetadata> getMetadata(Instance instance, ZoneId zone, Optional<DeploymentInstanceSpec> instanceSpec) { + Instant start = clock.instant(); + Optional<EndpointCertificateMetadata> metadata = getOrProvision(instance, zone, instanceSpec); + metadata.ifPresent(m -> curator.writeEndpointCertificateMetadata(instance.id(), m.withLastRequested(clock.instant().getEpochSecond()))); + Duration duration = Duration.between(start, clock.instant()); + if (duration.toSeconds() > 30) + log.log(Level.INFO, String.format("Getting endpoint certificate metadata for %s took %d seconds!", instance.id().serializedForm(), duration.toSeconds())); + return metadata; + } + + private Optional<EndpointCertificateMetadata> getOrProvision(Instance instance, ZoneId zone, Optional<DeploymentInstanceSpec> instanceSpec) { + final var currentCertificateMetadata = curator.readEndpointCertificateMetadata(instance.id()); + + DeploymentId deployment = new DeploymentId(instance.id(), zone); + + if (currentCertificateMetadata.isEmpty()) { + var provisionedCertificateMetadata = provisionEndpointCertificate(deployment, Optional.empty(), instanceSpec); + // We do not verify the certificate if one has never existed before - because we do not want to + // wait for it to be available before we deploy. This allows the config server to start + // provisioning nodes ASAP, and the risk is small for a new deployment. + curator.writeEndpointCertificateMetadata(instance.id(), provisionedCertificateMetadata); + return Optional.of(provisionedCertificateMetadata); + } + + // Re-provision certificate if it is missing SANs for the zone we are deploying to + var requiredSansForZone = controller.routing().certificateDnsNames(deployment); + if (!currentCertificateMetadata.get().requestedDnsSans().containsAll(requiredSansForZone)) { + var reprovisionedCertificateMetadata = + provisionEndpointCertificate(deployment, currentCertificateMetadata, instanceSpec) + .withRequestId(currentCertificateMetadata.get().requestId()); // We're required to keep the original request ID + curator.writeEndpointCertificateMetadata(instance.id(), reprovisionedCertificateMetadata); + // Verification is unlikely to succeed in this case, as certificate must be available first - controller will retry + certificateValidator.validate(reprovisionedCertificateMetadata, instance.id().serializedForm(), zone, requiredSansForZone); + return Optional.of(reprovisionedCertificateMetadata); + } + + certificateValidator.validate(currentCertificateMetadata.get(), instance.id().serializedForm(), zone, requiredSansForZone); + return currentCertificateMetadata; + } + + private EndpointCertificateMetadata provisionEndpointCertificate(DeploymentId deployment, + Optional<EndpointCertificateMetadata> currentMetadata, + Optional<DeploymentInstanceSpec> instanceSpec) { + List<ZoneId> zonesInSystem = controller.zoneRegistry().zones().controllerUpgraded().ids(); + Set<ZoneId> requiredZones = new LinkedHashSet<>(); + requiredZones.add(deployment.zoneId()); + if (!deployment.zoneId().environment().isManuallyDeployed()) { + // If not deploying to a dev or perf zone, require all prod zones in deployment spec + test and staging + zonesInSystem.stream() + .filter(zone -> zone.environment().isTest() || + (instanceSpec.isPresent() && + instanceSpec.get().deploysTo(zone.environment(), zone.region()))) + .forEach(requiredZones::add); + } + Set<String> requiredNames = requiredZones.stream() + .flatMap(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone)).stream()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + // Preserve any currently present names that are still valid + List<String> currentNames = currentMetadata.map(EndpointCertificateMetadata::requestedDnsSans) + .orElseGet(List::of); + zonesInSystem.stream() + .map(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone))) + .filter(currentNames::containsAll) + .forEach(requiredNames::addAll); + + return certificateProvider.requestCaSignedCertificate(deployment.applicationId(), List.copyOf(requiredNames), currentMetadata); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java index d874a8042f2..1c204159d2c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java @@ -22,9 +22,10 @@ public class ConvergenceSummary { private final long restarting; private final long services; private final long needNewConfig; + private final long retiring; public ConvergenceSummary(long nodes, long down, long upgradingOs, long upgradingFirmware, long needPlatformUpgrade, long upgradingPlatform, - long needReboot, long rebooting, long needRestart, long restarting, long services, long needNewConfig) { + long needReboot, long rebooting, long needRestart, long restarting, long services, long needNewConfig, long retiring) { this.nodes = nodes; this.down = down; this.upgradingOs = upgradingOs; @@ -37,6 +38,7 @@ public class ConvergenceSummary { this.restarting = restarting; this.services = services; this.needNewConfig = needNewConfig; + this.retiring = retiring; } /** Number of nodes in the application. */ @@ -99,6 +101,11 @@ public class ConvergenceSummary { return needNewConfig; } + /** Number of nodes that are retiring. */ + public long retiring() { + return retiring; + } + /** Whether the convergence is done. */ public boolean converged() { return nodes > 0 @@ -125,12 +132,13 @@ public class ConvergenceSummary { needRestart == that.needRestart && restarting == that.restarting && services == that.services && - needNewConfig == that.needNewConfig; + needNewConfig == that.needNewConfig && + retiring == that.retiring; } @Override public int hashCode() { - return Objects.hash(nodes, down, upgradingOs, upgradingFirmware, needPlatformUpgrade, upgradingPlatform, needReboot, rebooting, needRestart, restarting, services, needNewConfig); + return Objects.hash(nodes, down, upgradingOs, upgradingFirmware, needPlatformUpgrade, upgradingPlatform, needReboot, rebooting, needRestart, restarting, services, needNewConfig, retiring); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java index 5bd43dfd695..e9755b7fd8b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java @@ -426,21 +426,20 @@ public class InternalStepRunner implements StepRunner { return Optional.empty(); } - /** Returns true iff all containers in the deployment give 100 consecutive 200 OK responses on /status.html. */ + /** Returns true iff all calls to endpoint in the deployment give 100 consecutive 200 OK responses on /status.html. */ private boolean containersAreUp(ApplicationId id, ZoneId zoneId, DualLogger logger) { var endpoints = controller.routing().zoneEndpointsOf(Set.of(new DeploymentId(id, zoneId))); if ( ! endpoints.containsKey(zoneId)) return false; - for (var endpoint : endpoints.get(zoneId)) { + return endpoints.get(zoneId).parallelStream().map(endpoint -> { boolean ready = controller.jobController().cloud().ready(endpoint.url()); if ( ! ready) { logger.log("Failed to get 100 consecutive OKs from " + endpoint); - return false; + return Boolean.FALSE; } - } - - return true; + return Boolean.TRUE; + }).allMatch(Boolean.TRUE::equals); } /** Returns true iff all containers in the tester deployment give 100 consecutive 200 OK responses on /status.html. */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java index 080d600005f..3c3cbfe2b2a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java @@ -87,6 +87,12 @@ public class NodeList extends AbstractFilteringList<NodeWithServices, NodeList> return matching(NodeWithServices::needsNewConfig); } + /** The nodes that are retiring. */ + public NodeList retiring() { + return matching(node -> node.node().retired()); + } + + /** Returns a summary of the convergence status of the nodes in this list. */ public ConvergenceSummary summary() { NodeList allowedDown = expectedDown(); @@ -101,7 +107,8 @@ public class NodeList extends AbstractFilteringList<NodeWithServices, NodeList> needsRestart().size(), allowedDown.needsRestart().size(), asList().stream().mapToLong(node -> node.services().size()).sum(), - asList().stream().mapToLong(node -> node.services().stream().filter(service -> wantedConfigGeneration > service.currentGeneration()).count()).sum()); + asList().stream().mapToLong(node -> node.services().stream().filter(service -> wantedConfigGeneration > service.currentGeneration()).count()).sum(), + retiring().size()); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java index 6bda6dc8a25..a8214ac8a09 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java @@ -61,7 +61,6 @@ public class DeploymentMetricsMaintainer extends ControllerMaintainer { DeploymentId deploymentId = new DeploymentId(instance.id(), deployment.zone()); List<ClusterMetrics> clusterMetrics = controller().serviceRegistry().configServer().getDeploymentMetrics(deploymentId); Instant now = controller().clock().instant(); - applications.lockApplicationIfPresent(application.id(), locked -> { Deployment existingDeployment = locked.get().require(instance.name()).deployments().get(deployment.zone()); if (existingDeployment == null) return; // Deployment removed since we started collecting metrics diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java index 111a332bc81..fedf3d90760 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainer.java @@ -15,6 +15,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequest.Impa import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequestClient; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.HostAction; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.HostAction.State; +import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VCMRReport; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VespaChangeRequest; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VespaChangeRequest.Status; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; @@ -144,12 +145,15 @@ public class VCMRMaintainer extends ControllerMaintainer { if (changeRequest.getChangeRequestSource().isClosed()) { logger.fine(() -> changeRequest.getChangeRequestSource().getId() + " is closed, recycling " + node.hostname()); recycleNode(changeRequest.getZoneId(), node, hostAction); + removeReport(changeRequest, node); return hostAction.withState(State.COMPLETE); } if (isLowImpact(changeRequest)) return hostAction; + addReport(changeRequest, node); + if (isPostponed(changeRequest, hostAction)) { logger.fine(() -> changeRequest.getChangeRequestSource().getId() + " is postponed, recycling " + node.hostname()); recycleNode(changeRequest.getZoneId(), node, hostAction); @@ -285,4 +289,28 @@ public class VCMRMaintainer extends ControllerMaintainer { logger.info("Approving " + changeRequest.getChangeRequestSource().getId()); changeRequestClient.approveChangeRequest(changeRequest); } + + private void removeReport(VespaChangeRequest changeRequest, Node node) { + var report = VCMRReport.fromReports(node.reports()); + + if (report.removeVcmr(changeRequest.getChangeRequestSource().getId())) { + updateReport(changeRequest.getZoneId(), node, report); + } + } + + private void addReport(VespaChangeRequest changeRequest, Node node) { + var report = VCMRReport.fromReports(node.reports()); + + var source = changeRequest.getChangeRequestSource(); + if (report.addVcmr(source.getId(), source.getPlannedStartTime(), source.getPlannedEndTime())) { + updateReport(changeRequest.getZoneId(), node, report); + } + } + + private void updateReport(ZoneId zoneId, Node node, VCMRReport report) { + logger.info(String.format("Updating report for %s: %s", node.hostname(), report)); + var newNode = new NodeRepositoryNode(); + newNode.setReports(report.toNodeReports()); + nodeRepository.patchNode(zoneId, node.hostname().value(), newNode); + } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateMetadataSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateMetadataSerializer.java index 7e4fc285793..3ced0d60453 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateMetadataSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateMetadataSerializer.java @@ -46,7 +46,7 @@ public class EndpointCertificateMetadataSerializer { object.setString(certNameField, metadata.certName()); object.setLong(versionField, metadata.version()); object.setLong(lastRequestedField, metadata.lastRequested()); - object.setString(requestIdField, metadata.request_id()); + object.setString(requestIdField, metadata.requestId()); var cursor = object.setArray(requestedDnsSansField); metadata.requestedDnsSans().forEach(cursor::addString); object.setString(issuerField, metadata.issuer()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java index 0ecd86a4a38..91d85e62fcb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java @@ -95,7 +95,8 @@ class RunSerializer { private static final String lastTestRecordField = "lastTestRecord"; private static final String lastVespaLogTimestampField = "lastVespaLogTimestamp"; private static final String noNodesDownSinceField = "noNodesDownSince"; - private static final String convergenceSummaryField = "convergenceSummary"; + private static final String oldConvergenceSummaryField = "convergenceSummary"; // TODO (freva): Remove after 7.410 + private static final String convergenceSummaryField = "convergenceSummaryV2"; private static final String testerCertificateField = "testerCertificate"; Run runFromSlime(Slime slime) { @@ -136,7 +137,8 @@ class RunSerializer { runObject.field(lastTestRecordField).asLong(), Instant.EPOCH.plus(runObject.field(lastVespaLogTimestampField).asLong(), ChronoUnit.MICROS), Serializers.optionalInstant(runObject.field(noNodesDownSinceField)), - convergenceSummaryFrom(runObject.field(convergenceSummaryField)), + convergenceSummaryFrom(runObject.field(convergenceSummaryField)) + .or(() ->convergenceSummaryFrom(runObject.field(oldConvergenceSummaryField))), Optional.of(runObject.field(testerCertificateField)) .filter(Inspector::valid) .map(certificate -> X509CertificateUtils.fromPem(certificate.asString()))); @@ -178,11 +180,10 @@ class RunSerializer { // Don't change this — introduce a separate array instead. private Optional<ConvergenceSummary> convergenceSummaryFrom(Inspector summaryArray) { - if ( ! summaryArray.valid() || summaryArray.entries() == 11) // TODO jonmv: fix - return Optional.empty(); + if ( ! summaryArray.valid()) return Optional.empty(); - if (summaryArray.entries() != 12) - throw new IllegalArgumentException("Convergence summary must have 12 entries"); + if (summaryArray.entries() != 12 && summaryArray.entries() != 13) + throw new IllegalArgumentException("Convergence summary must have 13 entries"); return Optional.of(new ConvergenceSummary(summaryArray.entry(0).asLong(), summaryArray.entry(1).asLong(), @@ -195,7 +196,8 @@ class RunSerializer { summaryArray.entry(8).asLong(), summaryArray.entry(9).asLong(), summaryArray.entry(10).asLong(), - summaryArray.entry(11).asLong())); + summaryArray.entry(11).asLong(), + summaryArray.entry(12).asLong())); } Slime toSlime(Iterable<Run> runs) { @@ -221,7 +223,10 @@ class RunSerializer { runObject.setLong(lastTestRecordField, run.lastTestLogEntry()); runObject.setLong(lastVespaLogTimestampField, Instant.EPOCH.until(run.lastVespaLogTimestamp(), ChronoUnit.MICROS)); run.noNodesDownSince().ifPresent(noNodesDownSince -> runObject.setLong(noNodesDownSinceField, noNodesDownSince.toEpochMilli())); - run.convergenceSummary().ifPresent(convergenceSummary -> toSlime(convergenceSummary, runObject.setArray(convergenceSummaryField))); + run.convergenceSummary().ifPresent(convergenceSummary -> { + toSlime(convergenceSummary, runObject.setArray(convergenceSummaryField), false); + toSlime(convergenceSummary, runObject.setArray(oldConvergenceSummaryField), true); + }); run.testerCertificate().ifPresent(certificate -> runObject.setString(testerCertificateField, X509CertificateUtils.toPem(certificate))); Cursor stepsObject = runObject.setObject(stepsField); @@ -258,7 +263,7 @@ class RunSerializer { } // Don't change this - introduce a separate array with new values if needed. - private void toSlime(ConvergenceSummary summary, Cursor summaryArray) { + private void toSlime(ConvergenceSummary summary, Cursor summaryArray, boolean oldFormat) { summaryArray.addLong(summary.nodes()); summaryArray.addLong(summary.down()); summaryArray.addLong(summary.upgradingOs()); @@ -271,6 +276,8 @@ class RunSerializer { summaryArray.addLong(summary.restarting()); summaryArray.addLong(summary.services()); summaryArray.addLong(summary.needNewConfig()); + if (!oldFormat) + summaryArray.addLong(summary.retiring()); } static String valueOf(Step step) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java index 622a7033519..dba618d139c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java @@ -213,6 +213,7 @@ class JobControllerApiHandlerHelper { summaryObject.setLong("upgradingFirmware", summary.upgradingFirmware()); summaryObject.setLong("services", summary.services()); summaryObject.setLong("needNewConfig", summary.needNewConfig()); + summaryObject.setLong("retiring", summary.retiring()); } private static void toSlime(Cursor entryArray, List<LogEntry> entries) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java index daa84f4700c..f0bcd2b4d03 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java @@ -250,7 +250,11 @@ public class BillingApiHandler extends LoggingRequestHandler { var invoiceId = billingController.createInvoiceForPeriod(tenantName, startTime, endTime, userId); - return new MessageResponse("Created invoice with ID " + invoiceId.value()); + Slime slime = new Slime(); + Cursor root = slime.setObject(); + root.setString("message", "Created invoice with ID " + invoiceId.value()); + root.setString("id", invoiceId.value()); + return new SlimeJsonResponse(slime); } private HttpResponse getInstruments(String tenant, String userId) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java index ff24253128b..e2922c040d2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java @@ -18,7 +18,12 @@ import com.yahoo.yolean.Exceptions; import java.io.IOException; import java.io.OutputStream; +import java.time.Instant; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,6 +40,7 @@ public class BadgeApiHandler extends LoggingRequestHandler { private final static Logger log = Logger.getLogger(BadgeApiHandler.class.getName()); private final Controller controller; + private final Map<Key, Value> badgeCache = new ConcurrentHashMap<>(); public BadgeApiHandler(Context parentCtx, Controller controller) { super(parentCtx); @@ -59,29 +65,44 @@ public class BadgeApiHandler extends LoggingRequestHandler { private HttpResponse get(HttpRequest request) { Path path = new Path(request.getUri()); - if (path.matches("/badge/v1/{tenant}/{application}/{instance}")) return badge(path.get("tenant"), path.get("application"), path.get("instance")); - if (path.matches("/badge/v1/{tenant}/{application}/{instance}/{jobName}")) return badge(path.get("tenant"), path.get("application"), path.get("instance"), path.get("jobName"), request.getProperty("historyLength")); + if (path.matches("/badge/v1/{tenant}/{application}/{instance}")) return overviewBadge(path.get("tenant"), path.get("application"), path.get("instance")); + if (path.matches("/badge/v1/{tenant}/{application}/{instance}/{jobName}")) return historyBadge(path.get("tenant"), path.get("application"), path.get("instance"), path.get("jobName"), request.getProperty("historyLength")); return ErrorResponse.notFoundError(String.format("No '%s' handler at '%s'", request.getMethod(), request.getUri().getPath())); } /** Returns a URI which points to an overview badge for the given application. */ - private HttpResponse badge(String tenant, String application, String instance) { + private HttpResponse overviewBadge(String tenant, String application, String instance) { ApplicationId id = ApplicationId.from(tenant, application, instance); - DeploymentStatus status = controller.jobController().deploymentStatus(controller.applications().requireApplication(TenantAndApplicationId.from(id))); - Predicate<JobStatus> isDeclaredJob = job -> status.jobSteps().get(job.id()) != null && status.jobSteps().get(job.id()).isDeclared(); - return svgResponse(Badges.overviewBadge(id, - status.jobs().instance(id.instance()).matching(isDeclaredJob), - controller.system())); + return cachedResponse(new Key(id, null, 0), + controller.clock().instant(), + () -> { + DeploymentStatus status = controller.jobController().deploymentStatus(controller.applications().requireApplication(TenantAndApplicationId.from(id))); + Predicate<JobStatus> isDeclaredJob = job -> status.jobSteps().get(job.id()) != null && status.jobSteps().get(job.id()).isDeclared(); + return Badges.overviewBadge(id, + status.jobs().instance(id.instance()).matching(isDeclaredJob), + controller.system()); + }); } /** Returns a URI which points to a history badge for the given application and job type. */ - private HttpResponse badge(String tenant, String application, String instance, String jobName, String historyLength) { + private HttpResponse historyBadge(String tenant, String application, String instance, String jobName, String historyLength) { ApplicationId id = ApplicationId.from(tenant, application, instance); - return svgResponse(Badges.historyBadge(id, - controller.jobController().jobStatus(new JobId(id, JobType.fromJobName(jobName))), - historyLength == null ? 5 : Math.min(32, Math.max(0, Integer.parseInt(historyLength))))); + JobType type = JobType.fromJobName(jobName); + int length = historyLength == null ? 5 : Math.min(32, Math.max(0, Integer.parseInt(historyLength))); + return cachedResponse(new Key(id, type, length), + controller.clock().instant(), + () -> Badges.historyBadge(id, + controller.jobController().jobStatus(new JobId(id, type)), + length) + ); + } + + private HttpResponse cachedResponse(Key key, Instant now, Supplier<String> badge) { + return svgResponse(badgeCache.compute(key, (__, value) -> { + return value != null && value.expiry.isAfter(now) ? value : new Value(badge.get(), now); + }).badgeSvg); } private static HttpResponse svgResponse(String svg) { @@ -95,4 +116,44 @@ public class BadgeApiHandler extends LoggingRequestHandler { }; } + + private static class Key { + + private final ApplicationId id; + private final JobType type; + private final int historyLength; + + private Key(ApplicationId id, JobType type, int historyLength) { + this.id = id; + this.type = type; + this.historyLength = historyLength; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Key key = (Key) o; + return historyLength == key.historyLength && id.equals(key.id) && type == key.type; + } + + @Override + public int hashCode() { + return Objects.hash(id, type, historyLength); + } + + } + + private static class Value { + + private final String badgeSvg; + private final Instant expiry; + + private Value(String badgeSvg, Instant created) { + this.badgeSvg = badgeSvg; + this.expiry = created.plusSeconds(60); + } + + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java index 2b3f954c2ef..4951f2ba33b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java @@ -111,13 +111,12 @@ public class Badges { Run lastTriggered = status.lastTriggered().get(); List<Run> runs = status.runs().descendingMap().values().stream() - .filter(Run::hasEnded) - .limit(length + (lastTriggered.hasEnded() ? 0 : 1)) - .collect(toList()); + .filter(Run::hasEnded) + .skip(1) + .limit(length + (lastTriggered.hasEnded() ? 0 : 1)) + .collect(toList()); - boolean isOk = runs.isEmpty() || runs.get(0).status() == RunStatus.success; - if ( ! lastTriggered.hasEnded()) - runs.remove(0); + boolean isOk = status.lastCompleted().map(run -> run.status() == RunStatus.success).orElse(true); text = lastTriggered.id().type().jobName(); textWidth = widthOf(text); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManagerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java index e30f044c579..d8544ff3947 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificateManagerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java @@ -12,22 +12,22 @@ import com.yahoo.security.KeyUtils; import com.yahoo.security.SignatureAlgorithm; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; +import com.yahoo.test.ManualClock; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.InMemoryFlagSource; +import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.Instance; -import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata; +import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorImpl; import com.yahoo.vespa.hosted.controller.integration.SecretStoreMock; -import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock; -import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; +import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import org.junit.Before; import org.junit.Test; import javax.security.auth.x500.X500Principal; import java.security.KeyPair; import java.security.cert.X509Certificate; -import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -41,17 +41,19 @@ import static org.junit.Assert.assertTrue; /** * @author andreer */ -public class EndpointCertificateManagerTest { +public class EndpointCertificatesTest { + private final ControllerTester tester = new ControllerTester(); private final SecretStoreMock secretStore = new SecretStoreMock(); - private final ZoneRegistryMock zoneRegistryMock = new ZoneRegistryMock(SystemName.main); - private final MockCuratorDb mockCuratorDb = new MockCuratorDb(); + private final CuratorDb mockCuratorDb = tester.curator(); + private final ManualClock clock = tester.clock(); private final EndpointCertificateMock endpointCertificateMock = new EndpointCertificateMock(); - private final InMemoryFlagSource inMemoryFlagSource = new InMemoryFlagSource(); - private static final Clock clock = Clock.fixed(Instant.EPOCH, java.time.ZoneId.systemDefault()); private final EndpointCertificateValidatorImpl endpointCertificateValidator = new EndpointCertificateValidatorImpl(secretStore, clock); - private final EndpointCertificateManager endpointCertificateManager = - new EndpointCertificateManager(zoneRegistryMock, mockCuratorDb, endpointCertificateMock, endpointCertificateValidator, clock); + private final EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateMock, endpointCertificateValidator); + private final KeyPair testKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 192); + + private X509Certificate testCertificate; + private X509Certificate testCertificate2; private static final List<String> expectedSans = List.of( "vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud", @@ -81,11 +83,7 @@ public class EndpointCertificateManagerTest { "*.default.default.us-east-1.dev.vespa.oath.cloud" ); - private static final KeyPair testKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 192); - private static final X509Certificate testCertificate = makeTestCert(expectedSans); - private static final X509Certificate testCertificate2 = makeTestCert(expectedCombinedSans); - - private static X509Certificate makeTestCert(List<String> sans) { + private X509Certificate makeTestCert(List<String> sans) { X509CertificateBuilder x509CertificateBuilder = X509CertificateBuilder .fromKeypair( testKeyPair, @@ -104,14 +102,17 @@ public class EndpointCertificateManagerTest { @Before public void setUp() { - zoneRegistryMock.exclusiveRoutingIn(zoneRegistryMock.zones().all().zones()); - testZone = zoneRegistryMock.zones().directlyRouted().in(Environment.prod).zones().stream().findFirst().orElseThrow().getId(); + tester.zoneRegistry().exclusiveRoutingIn(tester.zoneRegistry().zones().all().zones()); + testZone = tester.zoneRegistry().zones().directlyRouted().in(Environment.prod).zones().stream().findFirst().orElseThrow().getId(); + clock.setInstant(Instant.EPOCH); + testCertificate = makeTestCert(expectedSans); + testCertificate2 = makeTestCert(expectedCombinedSans); } @Test public void provisions_new_certificate_in_dev() { - ZoneId testZone = zoneRegistryMock.zones().directlyRouted().in(Environment.dev).zones().stream().findFirst().orElseThrow().getId(); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(testInstance, testZone, Optional.empty()); + ZoneId testZone = tester.zoneRegistry().zones().directlyRouted().in(Environment.dev).zones().stream().findFirst().orElseThrow().getId(); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); @@ -121,7 +122,40 @@ public class EndpointCertificateManagerTest { @Test public void provisions_new_certificate_in_prod() { - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); + assertTrue(endpointCertificateMetadata.isPresent()); + assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); + assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); + assertEquals(0, endpointCertificateMetadata.get().version()); + assertEquals(expectedSans, endpointCertificateMetadata.get().requestedDnsSans()); + } + + @Test + public void provisions_new_certificate_in_public_prod() { + ControllerTester tester = new ControllerTester(SystemName.Public); + ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.VESPA_APP_DOMAIN_IN_CERTIFICATE.id(), true); + EndpointCertificateValidatorImpl endpointCertificateValidator = new EndpointCertificateValidatorImpl(secretStore, clock); + EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateMock, endpointCertificateValidator); + List<String> expectedSans = List.of( + "vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.public.vespa.oath.cloud", + "default.default.global.public.vespa.oath.cloud", + "default.default.g.vespa-app.cloud", + "*.default.default.global.public.vespa.oath.cloud", + "*.default.default.g.vespa-app.cloud", + "default.default.aws-us-east-1a.public.vespa.oath.cloud", + "default.default.aws-us-east-1a.z.vespa-app.cloud", + "*.default.default.aws-us-east-1a.public.vespa.oath.cloud", + "*.default.default.aws-us-east-1a.z.vespa-app.cloud", + "default.default.aws-us-east-1c.test.public.vespa.oath.cloud", + "default.default.aws-us-east-1c.test.z.vespa-app.cloud", + "*.default.default.aws-us-east-1c.test.public.vespa.oath.cloud", + "*.default.default.aws-us-east-1c.test.z.vespa-app.cloud", + "default.default.aws-us-east-1c.staging.public.vespa.oath.cloud", + "default.default.aws-us-east-1c.staging.z.vespa-app.cloud", + "*.default.default.aws-us-east-1c.staging.public.vespa.oath.cloud", + "*.default.default.aws-us-east-1c.staging.z.vespa-app.cloud" + ); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); @@ -140,7 +174,7 @@ public class EndpointCertificateManagerTest { "", Optional.empty(), Optional.empty())); secretStore.setSecret(testKeyName, KeyUtils.toPem(testKeyPair.getPrivate()), 7); secretStore.setSecret(testCertName, X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 7); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); assertTrue(endpointCertificateMetadata.isPresent()); assertEquals(testKeyName, endpointCertificateMetadata.get().keyName()); assertEquals(testCertName, endpointCertificateMetadata.get().certName()); @@ -152,7 +186,7 @@ public class EndpointCertificateManagerTest { mockCuratorDb.writeEndpointCertificateMetadata(testInstance.id(), new EndpointCertificateMetadata(testKeyName, testCertName, -1, 0, "uuid", List.of(), "issuer", Optional.empty(), Optional.empty())); secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 0); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); assertTrue(endpointCertificateMetadata.isPresent()); assertEquals(0, endpointCertificateMetadata.get().version()); assertEquals(endpointCertificateMetadata, mockCuratorDb.readEndpointCertificateMetadata(testInstance.id())); @@ -160,7 +194,7 @@ public class EndpointCertificateManagerTest { @Test public void reprovisions_certificate_with_added_sans_when_deploying_to_new_zone() { - ZoneId testZone = zoneRegistryMock.zones().directlyRouted().in(Environment.prod).zones().stream().skip(1).findFirst().orElseThrow().getId(); + ZoneId testZone = tester.zoneRegistry().zones().directlyRouted().in(Environment.prod).zones().stream().skip(1).findFirst().orElseThrow().getId(); mockCuratorDb.writeEndpointCertificateMetadata(testInstance.id(), new EndpointCertificateMetadata(testKeyName, testCertName, -1, 0, "original-request-uuid", expectedSans, "mockCa", Optional.empty(), Optional.empty())); secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), -1); @@ -169,11 +203,11 @@ public class EndpointCertificateManagerTest { secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate2) + X509CertificateUtils.toPem(testCertificate2), 0); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(testInstance, testZone, Optional.empty()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.empty()); assertTrue(endpointCertificateMetadata.isPresent()); assertEquals(0, endpointCertificateMetadata.get().version()); assertEquals(endpointCertificateMetadata, mockCuratorDb.readEndpointCertificateMetadata(testInstance.id())); - assertEquals("original-request-uuid", endpointCertificateMetadata.get().request_id()); + assertEquals("original-request-uuid", endpointCertificateMetadata.get().requestId()); assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(endpointCertificateMetadata.get().requestedDnsSans())); } @@ -190,12 +224,13 @@ public class EndpointCertificateManagerTest { " </instance>\n" + "</deployment>\n"); - ZoneId testZone = zoneRegistryMock.zones().controllerUpgraded().in(Environment.staging).zones().stream().findFirst().orElseThrow().getId(); - Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificateManager.getEndpointCertificateMetadata(testInstance, testZone, Optional.of(deploymentSpec.requireInstance("default"))); + ZoneId testZone = tester.zoneRegistry().zones().all().in(Environment.staging).zones().stream().findFirst().orElseThrow().getId(); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = endpointCertificates.getMetadata(testInstance, testZone, Optional.of(deploymentSpec.requireInstance("default"))); assertTrue(endpointCertificateMetadata.isPresent()); assertTrue(endpointCertificateMetadata.get().keyName().matches("vespa.tls.default.default.*-key")); assertTrue(endpointCertificateMetadata.get().certName().matches("vespa.tls.default.default.*-cert")); assertEquals(0, endpointCertificateMetadata.get().version()); assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(endpointCertificateMetadata.get().requestedDnsSans())); } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java index fe241976d13..afb56f10c38 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java @@ -292,6 +292,8 @@ public class NodeRepositoryMock implements NodeRepository { newNode.modelName(node.getModelName()); if (node.getWantToRetire() != null) newNode.wantToRetire(node.getWantToRetire()); + if (!node.getReports().isEmpty()) + newNode.reports(node.getReports()); putNodes(zoneId, newNode.build()); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainerTest.java index c2983b6343d..16ed6b7ef98 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VCMRMaintainerTest.java @@ -9,6 +9,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequest; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequestSource; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.HostAction; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.HostAction.State; +import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VCMRReport; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VespaChangeRequest; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VespaChangeRequest.Status; import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock; @@ -39,22 +40,32 @@ public class VCMRMaintainerTest { public void setup() { tester = new ControllerTester(); maintainer = new VCMRMaintainer(tester.controller(), Duration.ofMinutes(1)); - nodeRepo = tester.serviceRegistry().configServer().nodeRepository(); + nodeRepo = tester.serviceRegistry().configServer().nodeRepository().allowPatching(true); } @Test public void recycle_hosts_after_completion() { + var vcmrReport = new VCMRReport(); + vcmrReport.addVcmr("id123", ZonedDateTime.now(), ZonedDateTime.now()); var parkedNode = createNode(host1, NodeType.host, Node.State.parked, true); var failedNode = createNode(host2, NodeType.host, Node.State.failed, false); + parkedNode = new Node.Builder(parkedNode) + .reports(vcmrReport.toNodeReports()) + .build(); + nodeRepo.putNodes(zoneId, List.of(parkedNode, failedNode)); tester.curator().writeChangeRequest(canceledChangeRequest()); maintainer.maintain(); - // Only the parked node is recycled + // Only the parked node is recycled, VCMR report is cleared var nodeList = nodeRepo.list(zoneId, List.of(host1, host2)); assertEquals(Node.State.dirty, nodeList.get(0).state()); assertEquals(Node.State.failed, nodeList.get(1).state()); + + var report = nodeList.get(0).reports(); + assertNull(report.get(VCMRReport.getReportId())); + var writtenChangeRequest = tester.curator().readChangeRequest(changeRequestId).get(); assertEquals(Status.COMPLETED, writtenChangeRequest.getStatus()); } @@ -82,7 +93,7 @@ public class VCMRMaintainerTest { var activeNode = createNode(host1, NodeType.host, Node.State.active, false); var failedNode = createNode(host2, NodeType.host, Node.State.failed, false); nodeRepo.putNodes(zoneId, List.of(activeNode, failedNode)); - nodeRepo.allowPatching(true).hasSpareCapacity(true); + nodeRepo.hasSpareCapacity(true); tester.curator().writeChangeRequest(startingChangeRequest()); maintainer.maintain(); @@ -150,6 +161,13 @@ public class VCMRMaintainerTest { var approvedChangeRequests = tester.serviceRegistry().changeRequestClient().getApprovedChangeRequests(); assertEquals(1, approvedChangeRequests.size()); + + activeNode = nodeRepo.list(zoneId, List.of(host2)).get(0); + var report = VCMRReport.fromReports(activeNode.reports()); + var reportAdded = report.getVcmrs().stream() + .filter(vcmr -> vcmr.getId().equals(changeRequestId)) + .count() == 1; + assertTrue(reportAdded); } @Test @@ -157,7 +175,7 @@ public class VCMRMaintainerTest { var parkedNode = createNode(host1, NodeType.host, Node.State.parked, false); var retiringNode = createNode(host2, NodeType.host, Node.State.active, true); nodeRepo.putNodes(zoneId, List.of(parkedNode, retiringNode)); - nodeRepo.allowPatching(true).hasSpareCapacity(true); + nodeRepo.hasSpareCapacity(true); tester.curator().writeChangeRequest(postponedChangeRequest()); maintainer.maintain(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java index b0a0cdf6293..8df2fd87398 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java @@ -19,12 +19,14 @@ import com.yahoo.vespa.hosted.controller.deployment.StepInfo; import org.junit.Test; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; import java.util.Collections; import java.util.Optional; +import java.util.function.BiConsumer; import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted; import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running; @@ -101,7 +103,7 @@ public class RunSerializerTest { "badb17"), 122), run.versions().sourceApplication().get()); - assertEquals(Optional.of(new ConvergenceSummary(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144)), + assertEquals(Optional.of(new ConvergenceSummary(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233)), run.convergenceSummary()); assertEquals(X509CertificateUtils.fromPem("-----BEGIN CERTIFICATE-----\n" + "MIIBEzCBu6ADAgECAgEBMAoGCCqGSM49BAMEMBQxEjAQBgNVBAMTCW15c2Vydmlj\n" + @@ -153,4 +155,18 @@ public class RunSerializerTest { assertEquals(initial, serializer.runFromSlime(serializer.toSlime(initial))); } + @Test + public void convergenceSummaryMigrationTest() throws IOException { + String data = Files.readString(runFile); + BiConsumer<String, ConvergenceSummary> replaceAndAssert = (replace, convergenceSummaryOrNull) -> { + byte[] newData = data.replace("\"convergenceSummaryV2\": [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233],", replace).getBytes(StandardCharsets.UTF_8); + assertEquals(convergenceSummaryOrNull, serializer.runsFromSlime(SlimeUtils.jsonToSlime(newData)).get(id).convergenceSummary().orElse(null)); + }; + + replaceAndAssert.accept("", null); + replaceAndAssert.accept("\"convergenceSummary\": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],", new ConvergenceSummary(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0)); + replaceAndAssert.accept("\"convergenceSummaryV2\": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],\n" + + "\"convergenceSummary\": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],", new ConvergenceSummary(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json index a7e5d249a9d..0f40dd27664 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/testdata/run-status.json @@ -8,7 +8,7 @@ "lastTestRecord": 3, "lastVespaLogTimestamp": 1196676930000432, "noNodesDownSince": 321321321321, - "convergenceSummary": [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], + "convergenceSummaryV2": [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233], "testerCertificate": "-----BEGIN CERTIFICATE-----\nMIIBEzCBu6ADAgECAgEBMAoGCCqGSM49BAMEMBQxEjAQBgNVBAMTCW15c2Vydmlj\nZTAeFw0xOTA5MDYwNzM3MDZaFw0xOTA5MDcwNzM3MDZaMBQxEjAQBgNVBAMTCW15\nc2VydmljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM0JhD8fV2DlAkjQOGX3\nY50ryMBr3g2+v/uFiRoxJ1muuSOWYrW7HCQIGuzc04fa0QwtaX/voAZKCV51t6jF\n0fwwCgYIKoZIzj0EAwQDRwAwRAIgVbQ3Co1H4X0gmRrtXSyTU0HgBQu9PXHMmX20\n5MyyPSoCIBltOcmaPfdN03L3zqbqZ6PgUBWsvAHgiBzL3hrtJ+iy\n-----END CERTIFICATE-----", "steps": { "deployInitialReal": "unfinished", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1-log-first-part.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1-log-first-part.json index 895b49157db..72411d155c7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1-log-first-part.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1-log-first-part.json @@ -70,7 +70,8 @@ "upgradingOs": 0, "upgradingFirmware": 0, "services": 1, - "needNewConfig": 1 + "needNewConfig": 1, + "retiring": 0 } }, "copyVespaLogs": { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/staging-test-log.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/staging-test-log.json index 5aa13f1cf3c..2cf846ab6bb 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/staging-test-log.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/staging-test-log.json @@ -149,7 +149,8 @@ "upgradingOs": 0, "upgradingFirmware": 0, "services": 1, - "needNewConfig": 1 + "needNewConfig": 1, + "retiring": 0 } }, "startStagingSetup": { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response index 0a92229025b..fe9e8486e9b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response @@ -1 +1 @@ -{"message":"Created invoice with ID id-123"}
\ No newline at end of file +{"message":"Created invoice with ID id-123","id":"id-123"}
\ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java index ebde4671859..766a84ca33c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java @@ -13,6 +13,7 @@ import org.junit.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.Duration; /** * @author jonmv @@ -45,7 +46,7 @@ public class BadgeApiTest extends ControllerContainerTest { application.submit(applicationPackage) .runJob(JobType.systemTest) .runJob(JobType.stagingTest); - for (int i = 0; i < 32; i++) + for (int i = 0; i < 31; i++) application.failDeployment(JobType.productionUsWest1); application.triggerJobs(); tester.controller().applications().deploymentTrigger().reTrigger(application.instanceId(), JobType.testEuWest1); @@ -54,6 +55,17 @@ public class BadgeApiTest extends ControllerContainerTest { Files.readString(Paths.get(responseFiles + "overview.svg")), 200); tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=32"), Files.readString(Paths.get(responseFiles + "history.svg")), 200); + + // New change not reflected before cache entry expires. + tester.serviceRegistry().clock().advance(Duration.ofSeconds(59)); + application.runJob(JobType.productionUsWest1); + tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=32"), + Files.readString(Paths.get(responseFiles + "history.svg")), 200); + + // Cached entry refreshed after a minute. + tester.serviceRegistry().clock().advance(Duration.ofSeconds(1)); + tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=32"), + Files.readString(Paths.get(responseFiles + "history2.svg")), 200); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg index f466f38750c..0e30796bae2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg @@ -47,7 +47,7 @@ <rect x='646.1879570885093' rx='3' width='13.080134003280707' height='20' fill='#00f244'/> <rect x='646.1879570885093' rx='3' width='13.080134003280707' height='20' fill='url(#shade)'/> <rect x='646.4043039211865' rx='3' width='8' height='20' fill='url(#shadow)'/> - <rect x='639.1078230852286' rx='3' width='13.296480835957981' height='20' fill='#bf103c'/> + <rect x='639.1078230852286' rx='3' width='13.296480835957981' height='20' fill='#00f244'/> <rect x='639.1078230852286' rx='3' width='13.296480835957981' height='20' fill='url(#shade)'/> <rect x='639.3307808029524' rx='3' width='8' height='20' fill='url(#shadow)'/> <rect x='631.8113422492706' rx='3' width='13.519438553681752' height='20' fill='#bf103c'/> diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history2.svg b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history2.svg new file mode 100644 index 00000000000..73d65b08b69 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history2.svg @@ -0,0 +1,177 @@ +<svg xmlns='http://www.w3.org/2000/svg' width='659.058159125822' height='20' role='img' aria-label='Deployment Status'> + <title>Deployment Status</title> + <linearGradient id='light' x2='0' y2='100%'> + <stop offset='0' stop-color='#fff' stop-opacity='.5'/> + <stop offset='.1' stop-color='#fff' stop-opacity='.15'/> + <stop offset='.9' stop-color='#000' stop-opacity='.15'/> + <stop offset='1' stop-color='#000' stop-opacity='.5'/> + </linearGradient> + <linearGradient id='left-light' x2='100%' y2='0'> + <stop offset='0' stop-color='#fff' stop-opacity='.3'/> + <stop offset='.5' stop-color='#fff' stop-opacity='.1'/> + <stop offset='1' stop-color='#fff' stop-opacity='.0'/> + </linearGradient> + <linearGradient id='right-shadow' x2='100%' y2='0'> + <stop offset='0' stop-color='#000' stop-opacity='.0'/> + <stop offset='.5' stop-color='#000' stop-opacity='.1'/> + <stop offset='1' stop-color='#000' stop-opacity='.3'/> + </linearGradient> + <linearGradient id='shadow' x2='100%' y2='0'> + <stop offset='0' stop-color='#222' stop-opacity='.3'/> + <stop offset='.625' stop-color='#555' stop-opacity='.3'/> + <stop offset='.9' stop-color='#555' stop-opacity='.05'/> + <stop offset='1' stop-color='#555' stop-opacity='.0'/> + </linearGradient> + <linearGradient id='shade' x2='100%' y2='0'> + <stop offset='0' stop-color='#000' stop-opacity='.20'/> + <stop offset='0.05' stop-color='#000' stop-opacity='.10'/> + <stop offset='1' stop-color='#000' stop-opacity='.0'/> + </linearGradient> + <linearGradient id='run-on-failure' x1='40%' x2='80%' y2='0%'> + <stop offset='0' stop-color='#ab83ff' /> + <stop offset='1' stop-color='#bf103c' /> + <animate attributeName='x1' values='-110%;150%;20%;-110%' dur='6s' repeatCount='indefinite' /> + <animate attributeName='x2' values='-10%;250%;120%;-10%' dur='6s' repeatCount='indefinite' /> + </linearGradient> + <linearGradient id='run-on-success' x1='40%' x2='80%' y2='0%'> + <stop offset='0' stop-color='#ab83ff' /> + <stop offset='1' stop-color='#00f244' /> + <animate attributeName='x1' values='-110%;150%;20%;-110%' dur='6s' repeatCount='indefinite' /> + <animate attributeName='x2' values='-10%;250%;120%;-10%' dur='6s' repeatCount='indefinite' /> + </linearGradient> + <clipPath id='rounded'> + <rect width='659.058159125822' height='20' rx='3' fill='#fff'/> + </clipPath> + <g clip-path='url(#rounded)'> + <rect x='653.26809109179' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='646.1879570885093' rx='3' width='13.080134003280707' height='20' fill='#00f244'/> + <rect x='646.1879570885093' rx='3' width='13.080134003280707' height='20' fill='url(#shade)'/> + <rect x='646.4043039211865' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='639.1078230852286' rx='3' width='13.296480835957981' height='20' fill='#bf103c'/> + <rect x='639.1078230852286' rx='3' width='13.296480835957981' height='20' fill='url(#shade)'/> + <rect x='639.3307808029524' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='631.8113422492706' rx='3' width='13.519438553681752' height='20' fill='#bf103c'/> + <rect x='631.8113422492706' rx='3' width='13.519438553681752' height='20' fill='url(#shade)'/> + <rect x='632.0411128600877' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='624.2919036955889' rx='3' width='13.749209164498811' height='20' fill='#bf103c'/> + <rect x='624.2919036955889' rx='3' width='13.749209164498811' height='20' fill='url(#shade)'/> + <rect x='624.5286953802824' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='616.5426945310901' rx='3' width='13.986000849192376' height='20' fill='#bf103c'/> + <rect x='616.5426945310901' rx='3' width='13.986000849192376' height='20' fill='url(#shade)'/> + <rect x='616.7867218317995' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='608.5566936818977' rx='3' width='14.230028149901685' height='20' fill='#bf103c'/> + <rect x='608.5566936818977' rx='3' width='14.230028149901685' height='20' fill='url(#shade)'/> + <rect x='608.8081776965013' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='600.326665531996' rx='3' width='14.48151216450522' height='20' fill='#bf103c'/> + <rect x='600.326665531996' rx='3' width='14.48151216450522' height='20' fill='url(#shade)'/> + <rect x='600.5858341144344' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='591.8451533674908' rx='3' width='14.740680746943664' height='20' fill='#bf103c'/> + <rect x='591.8451533674908' rx='3' width='14.740680746943664' height='20' fill='url(#shade)'/> + <rect x='592.1122413342111' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='583.1044726205471' rx='3' width='15.007768713664104' height='20' fill='#bf103c'/> + <rect x='583.1044726205471' rx='3' width='15.007768713664104' height='20' fill='url(#shade)'/> + <rect x='583.3797219632555' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='574.096703906883' rx='3' width='15.283018056372542' height='20' fill='#bf103c'/> + <rect x='574.096703906883' rx='3' width='15.283018056372542' height='20' fill='url(#shade)'/> + <rect x='574.380364011798' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='564.8136858505105' rx='3' width='15.566678161287442' height='20' fill='#bf103c'/> + <rect x='564.8136858505105' rx='3' width='15.566678161287442' height='20' fill='url(#shade)'/> + <rect x='565.1060137243161' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='555.2470076892231' rx='3' width='15.859006035092985' height='20' fill='#bf103c'/> + <rect x='555.2470076892231' rx='3' width='15.859006035092985' height='20' fill='url(#shade)'/> + <rect x='555.5482681919269' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='545.3880016541301' rx='3' width='16.16026653779677' height='20' fill='#bf103c'/> + <rect x='545.3880016541301' rx='3' width='16.16026653779677' height='20' fill='url(#shade)'/> + <rect x='545.6984677390362' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='535.2277351163333' rx='3' width='16.470732622702883' height='20' fill='#bf103c'/> + <rect x='535.2277351163333' rx='3' width='16.470732622702883' height='20' fill='url(#shade)'/> + <rect x='535.5476880773482' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='524.7570024936304' rx='3' width='16.790685583717845' height='20' fill='#bf103c'/> + <rect x='524.7570024936304' rx='3' width='16.790685583717845' height='20' fill='url(#shade)'/> + <rect x='525.0867322201259' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='513.9663169099125' rx='3' width='17.12041531021341' height='20' fill='#bf103c'/> + <rect x='513.9663169099125' rx='3' width='17.12041531021341' height='20' fill='url(#shade)'/> + <rect x='514.3061221493763' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='502.84590159969906' rx='3' width='17.460220549677203' height='20' fill='#bf103c'/> + <rect x='502.84590159969906' rx='3' width='17.460220549677203' height='20' fill='url(#shade)'/> + <rect x='503.196090228411' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='491.38568105002184' rx='3' width='17.810409178389147' height='20' fill='#bf103c'/> + <rect x='491.38568105002184' rx='3' width='17.810409178389147' height='20' fill='url(#shade)'/> + <rect x='491.7465703520016' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='479.5752718716327' rx='3' width='18.171298480368904' height='20' fill='#bf103c'/> + <rect x='479.5752718716327' rx='3' width='18.171298480368904' height='20' fill='url(#shade)'/> + <rect x='479.9471888261109' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='467.40397339126383' rx='3' width='18.543215434847077' height='20' fill='#bf103c'/> + <rect x='467.40397339126383' rx='3' width='18.543215434847077' height='20' fill='url(#shade)'/> + <rect x='467.7872549689374' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='454.86075795641676' rx='3' width='18.92649701252067' height='20' fill='#bf103c'/> + <rect x='454.86075795641676' rx='3' width='18.92649701252067' height='20' fill='url(#shade)'/> + <rect x='455.25575142475725' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='441.9342609438961' rx='3' width='19.32149048086113' height='20' fill='#bf103c'/> + <rect x='441.9342609438961' rx='3' width='19.32149048086113' height='20' fill='url(#shade)'/> + <rect x='442.3413241817867' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='428.61277046303496' rx='3' width='19.728553718751726' height='20' fill='#bf103c'/> + <rect x='428.61277046303496' rx='3' width='19.728553718751726' height='20' fill='url(#shade)'/> + <rect x='429.03227228502243' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='414.8842167442832' rx='3' width='20.148055540739207' height='20' fill='#bf103c'/> + <rect x='414.8842167442832' rx='3' width='20.148055540739207' height='20' fill='url(#shade)'/> + <rect x='415.31653723473755' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='400.736161203544' rx='3' width='20.58037603119359' height='20' fill='#bf103c'/> + <rect x='400.736161203544' rx='3' width='20.58037603119359' height='20' fill='url(#shade)'/> + <rect x='401.1816920610293' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='386.1557851723504' rx='3' width='21.025906888678875' height='20' fill='#bf103c'/> + <rect x='386.1557851723504' rx='3' width='21.025906888678875' height='20' fill='url(#shade)'/> + <rect x='386.61493006451815' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='371.12987828367153' rx='3' width='21.485051780846597' height='20' fill='#bf103c'/> + <rect x='371.12987828367153' rx='3' width='21.485051780846597' height='20' fill='url(#shade)'/> + <rect x='371.60305321299876' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='355.6448265028249' rx='3' width='21.958226710173836' height='20' fill='#bf103c'/> + <rect x='355.6448265028249' rx='3' width='21.958226710173836' height='20' fill='url(#shade)'/> + <rect x='356.13246018352817' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='339.68659979265107' rx='3' width='22.445860390877083' height='20' fill='#bf103c'/> + <rect x='339.68659979265107' rx='3' width='22.445860390877083' height='20' fill='url(#shade)'/> + <rect x='340.18913403911733' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='323.24073940177396' rx='3' width='22.948394637343355' height='20' fill='#bf103c'/> + <rect x='323.24073940177396' rx='3' width='22.948394637343355' height='20' fill='url(#shade)'/> + <rect x='323.7586295288612' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='306.2923447644306' rx='3' width='23.466284764430597' height='20' fill='#bf103c'/> + <rect x='306.2923447644306' rx='3' width='23.466284764430597' height='20' fill='url(#shade)'/> + <rect x='306.82606' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='288.82606' rx='3' width='24.0' height='20' fill='#bf103c'/> + <rect x='288.82606' rx='3' width='24.0' height='20' fill='url(#shade)'/> + <rect x='288.82606' rx='3' width='8' height='20' fill='url(#shadow)'/> + <rect x='163.18729000000002' rx='3' width='131.63876999999997' height='20' fill='#00f244'/> + <rect x='163.18729000000002' rx='3' width='131.63876999999997' height='20' fill='url(#shade)'/> + <rect width='169.18729000000002' height='20' fill='#404040'/> + <rect x='-6.0' rx='3' width='175.18729000000002' height='20' fill='url(#shade)'/> + <rect width='2' height='20' fill='url(#left-light)'/> + <rect x='657.058159125822' width='2' height='20' fill='url(#right-shadow)'/> + <rect width='659.058159125822' height='20' fill='url(#light)'/> + </g> + <g fill='#fff' text-anchor='middle' font-family='Verdana,Geneva,DejaVu Sans,sans-serif' text-rendering='geometricPrecision' font-size='11'> + <svg x='6.5' y='3.0' width='16.0' height='16.0' viewBox='0 0 150 150'> + <polygon fill='#402a14' fill-opacity='0.5' points='84.84 10 34.1 44.46 34.1 103.78 84.84 68.02 135.57 103.78 135.57 44.46 84.84 10'/> + <polygon fill='#402a14' fill-opacity='0.5' points='84.84 68.02 84.84 10 135.57 44.46 135.57 103.78 84.84 68.02'/> + <polygon fill='#061a29' fill-opacity='0.5' points='65.07 81.99 14.34 46.22 14.34 105.54 65.07 140 115.81 105.54 115.81 46.22 65.07 81.99'/> + <polygon fill='#061a29' fill-opacity='0.5' points='65.07 81.99 65.07 140 14.34 105.54 14.34 46.22 65.07 81.99'/> + </svg> + <svg x='6.0' y='2.0' width='16.0' height='16.0' viewBox='0 0 150 150'> + <linearGradient id='yellow-shaded' x1='91.17' y1='44.83' x2='136.24' y2='73.4' gradientUnits='userSpaceOnUse'> + <stop offset='0.01' stop-color='#c6783e'/> + <stop offset='0.54' stop-color='#ff9750'/> + </linearGradient> + <linearGradient id='blue-shaded' x1='60.71' y1='104.56' x2='-15.54' y2='63' gradientUnits='userSpaceOnUse'> + <stop offset='0' stop-color='#005a8e'/> + <stop offset='0.54' stop-color='#1a7db6'/> + </linearGradient> + <polygon fill='#ff9d4b' points='84.84 10 34.1 44.46 34.1 103.78 84.84 68.02 135.57 103.78 135.57 44.46 84.84 10'/> + <polygon fill='url(#yellow-shaded)' points='84.84 68.02 84.84 10 135.57 44.46 135.57 103.78 84.84 68.02'/> + <polygon fill='#1a7db6' points='65.07 81.99 14.34 46.22 14.34 105.54 65.07 140 115.81 105.54 115.81 46.22 65.07 81.99'/> + <polygon fill='url(#blue-shaded)' points='65.07 81.99 65.07 140 14.34 105.54 14.34 46.22 65.07 81.99'/> + </svg> + <text font-size='11' x='96.09364500000001' y='15' fill='#000' fill-opacity='.4' textLength='135.18729000000002'>tenant.application.default</text> + <text font-size='11' x='95.59364500000001' y='14' fill='#fff' textLength='135.18729000000002'>tenant.application.default</text> + <text font-size='11' x='232.506675' y='15' fill='#000' fill-opacity='.4' textLength='113.63876999999998'>production-us-west-1</text> + <text font-size='11' x='232.006675' y='14' fill='#fff' textLength='113.63876999999998'>production-us-west-1</text> + </g> +</svg> diff --git a/dist/vespa.spec b/dist/vespa.spec index 2c75319da58..de92077fdbd 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -437,6 +437,28 @@ Requires: %{name}-base-libs = %{version}-%{release} Vespa - The open big data serving engine - tools +%package ann-benchmark + +Summary: Vespa - The open big data serving engine - ann-benchmark + +Requires: %{name}-libs = %{version}-%{release} +%if 0%{?el7} +Requires: python3 +%endif +%if 0%{?el8} +Requires: python36 +%endif +%if 0%{?fedora} +Requires: python3 +%endif + +%description ann-benchmark + +Vespa - The open big data serving engine - ann-benchmark + +Python binding for the Vespa implementation of an HNSW index for +nearest neighbor search used for low-level benchmarking. + %prep %if 0%{?installdir:1} %setup -c -D -T @@ -622,6 +644,7 @@ fi %{_prefix}/lib/jars/zookeeper-command-line-client-jar-with-dependencies.jar %{_prefix}/lib/perl5 %{_prefix}/libexec +%exclude %{_prefix}/libexec/vespa_ann_benchmark %exclude %{_prefix}/libexec/vespa/common-env.sh %exclude %{_prefix}/libexec/vespa/node-admin.sh %exclude %{_prefix}/libexec/vespa/standalone-container.sh @@ -703,7 +726,9 @@ fi %dir %{_prefix} %dir %{_prefix}/lib %dir %{_prefix}/lib/jars +%{_prefix}/bin/vespa-feed-client %{_prefix}/lib/jars/vespa-http-client-jar-with-dependencies.jar +%{_prefix}/lib/jars/vespa-feed-client-cli.jar %files config-model-fat %if %{_defattr_is_vespa_vespa} @@ -817,4 +842,12 @@ fi %dir %{_prefix}/lib/jars %{_prefix}/lib/jars/vespaclient-java-jar-with-dependencies.jar +%files ann-benchmark +%if %{_defattr_is_vespa_vespa} +%defattr(-,%{_vespa_user},%{_vespa_group},-) +%endif +%dir %{_prefix} +%dir %{_prefix}/libexec +%{_prefix}/libexec/vespa_ann_benchmark + %changelog diff --git a/document/src/main/java/com/yahoo/document/DataType.java b/document/src/main/java/com/yahoo/document/DataType.java index 104d63cae96..fd7ccfc5e96 100644 --- a/document/src/main/java/com/yahoo/document/DataType.java +++ b/document/src/main/java/com/yahoo/document/DataType.java @@ -54,7 +54,7 @@ public abstract class DataType extends Identifiable implements Serializable, Com public final static NumericDataType BYTE = new NumericDataType("byte", 16, ByteFieldValue.class, ByteFieldValue.getFactory()); public final static PrimitiveDataType PREDICATE = new PrimitiveDataType("predicate", 20, PredicateFieldValue.class, PredicateFieldValue.getFactory()); public final static int tensorDataTypeCode = 21; // All TensorDataType instances have id=21 but carries additional type information serialized separately - // ADDITIONAL parametrized types added at runtime: map, struct, array, weighted set, annotation reference, tensor + // ADDITIONAL parametrized types added at runtime: map, struct, array, weighted set, annotation reference, tensor // Tags are converted to weightedset<string> when reading the search definition TODO: Remove it public final static WeightedSetDataType TAG = new WeightedSetDataType(DataType.STRING, true, true); diff --git a/document/src/main/java/com/yahoo/document/json/TokenBuffer.java b/document/src/main/java/com/yahoo/document/json/TokenBuffer.java index 88353139b0f..9db80f3972b 100644 --- a/document/src/main/java/com/yahoo/document/json/TokenBuffer.java +++ b/document/src/main/java/com/yahoo/document/json/TokenBuffer.java @@ -29,7 +29,7 @@ public class TokenBuffer { } } - private Deque<Token> buffer; + private final Deque<Token> buffer; private int nesting = 0; public TokenBuffer() { diff --git a/document/src/main/java/com/yahoo/document/json/readers/TensorReader.java b/document/src/main/java/com/yahoo/document/json/readers/TensorReader.java index ad016a40fca..27426f584bd 100644 --- a/document/src/main/java/com/yahoo/document/json/readers/TensorReader.java +++ b/document/src/main/java/com/yahoo/document/json/readers/TensorReader.java @@ -11,6 +11,7 @@ import com.yahoo.tensor.TensorAddress; import com.yahoo.tensor.TensorType; import static com.yahoo.document.json.readers.JsonParserHelpers.*; +import static com.yahoo.tensor.serialization.JsonFormat.decodeHexString; /** * Reads the tensor format defined at @@ -41,7 +42,7 @@ public class TensorReader { else if (TENSOR_BLOCKS.equals(buffer.currentName())) readTensorBlocks(buffer, builder); else if (builder.type().dimensions().stream().anyMatch(d -> d.isIndexed())) // sparse can be empty - throw new IllegalArgumentException("Expected a tensor value to contain either 'cells', 'values' or 'blocks'"); + throw new IllegalArgumentException("Expected a tensor value to contain either 'cells', 'values' or 'blocks', but got: "+buffer.currentName()); } expectObjectEnd(buffer.currentToken()); tensorFieldValue.assign(builder.build()); @@ -91,10 +92,18 @@ public class TensorReader { throw new IllegalArgumentException("The 'values' field can only be used with dense tensors. " + "Use 'cells' or 'blocks' instead"); IndexedTensor.BoundBuilder indexedBuilder = (IndexedTensor.BoundBuilder)builder; + if (buffer.currentToken() == JsonToken.VALUE_STRING) { + double[] decoded = decodeHexString(buffer.currentText(), builder.type().valueType()); + for (int i = 0; i < decoded.length; i++) { + indexedBuilder.cellByDirectIndex(i, decoded[i]); + } + return; + } int index = 0; int initNesting = buffer.nesting(); - for (buffer.next(); buffer.nesting() >= initNesting; buffer.next()) + for (buffer.next(); buffer.nesting() >= initNesting; buffer.next()) { indexedBuilder.cellByDirectIndex(index++, readDouble(buffer)); + } expectCompositeEnd(buffer.currentToken()); } @@ -167,17 +176,21 @@ public class TensorReader { * @return the values read */ private static double[] readValues(TokenBuffer buffer, int size, TensorAddress address, TensorType type) { - expectArrayStart(buffer.currentToken()); - int index = 0; - int initNesting = buffer.nesting(); double[] values = new double[size]; - for (buffer.next(); buffer.nesting() >= initNesting; buffer.next()) - values[index++] = readDouble(buffer); + if (buffer.currentToken() == JsonToken.VALUE_STRING) { + values = decodeHexString(buffer.currentText(), type.valueType()); + index = values.length; + } else { + expectArrayStart(buffer.currentToken()); + int initNesting = buffer.nesting(); + for (buffer.next(); buffer.nesting() >= initNesting; buffer.next()) + values[index++] = readDouble(buffer); + expectCompositeEnd(buffer.currentToken()); + } if (index != size) throw new IllegalArgumentException((address != null ? "At " + address.toString(type) + ": " : "") + "Expected " + size + " values, but got " + index); - expectCompositeEnd(buffer.currentToken()); return values; } diff --git a/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java b/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java index da9ab4ea7bf..e50fd9734f7 100644 --- a/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java +++ b/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java @@ -164,10 +164,14 @@ public class JsonReaderTestCase { new TensorDataType(new TensorType.Builder().mapped("x").mapped("y").build()))); x.addField(new Field("dense_tensor", new TensorDataType(new TensorType.Builder().indexed("x", 2).indexed("y", 3).build()))); + x.addField(new Field("dense_int8_tensor", + new TensorDataType(TensorType.fromSpec("tensor<int8>(x[2],y[3])")))); x.addField(new Field("dense_unbound_tensor", new TensorDataType(new TensorType.Builder().indexed("x").indexed("y").build()))); x.addField(new Field("mixed_tensor", new TensorDataType(new TensorType.Builder().mapped("x").indexed("y", 3).build()))); + x.addField(new Field("mixed_bfloat16_tensor", + new TensorDataType(TensorType.fromSpec("tensor<bfloat16>(x{},y[3])")))); x.addField(new Field("mixed_tensor_adv", new TensorDataType(new TensorType.Builder().mapped("x").mapped("y").mapped("z").indexed("a", 3).build()))); types.registerDocumentType(x); @@ -1324,6 +1328,41 @@ public class JsonReaderTestCase { } @Test + public void testParsingOfDenseTensorHexFormat() { + Tensor.Builder builder = Tensor.Builder.of(TensorType.fromSpec("tensor<int8>(x[2],y[3])")); + builder.cell().label("x", 0).label("y", 0).value(2.0); + builder.cell().label("x", 0).label("y", 1).value(3.0); + builder.cell().label("x", 0).label("y", 2).value(4.0); + builder.cell().label("x", 1).label("y", 0).value(5.0); + builder.cell().label("x", 1).label("y", 1).value(6.0); + builder.cell().label("x", 1).label("y", 2).value(7.0); + Tensor expected = builder.build(); + Tensor tensor = assertTensorField(expected, + createPutWithTensor(inputJson("{", + " 'values': \"020304050607\"", + "}"), "dense_int8_tensor"), "dense_int8_tensor"); + assertTrue(tensor instanceof IndexedTensor); // this matters for performance + } + + @Test + public void testParsingOfMixedTensorHexFormat() { + Tensor.Builder builder = Tensor.Builder.of(TensorType.fromSpec("tensor<bfloat16>(x{},y[3])")); + builder.cell().label("x", "foo").label("y", 0).value(2.0); + builder.cell().label("x", "foo").label("y", 1).value(3.0); + builder.cell().label("x", "foo").label("y", 2).value(4.0); + builder.cell().label("x", "bar").label("y", 0).value(5.0); + builder.cell().label("x", "bar").label("y", 1).value(6.0); + builder.cell().label("x", "bar").label("y", 2).value(7.0); + Tensor expected = builder.build(); + String mixedJson = "{\"blocks\":[" + + "{\"address\":{\"x\":\"foo\"},\"values\":\"400040404080\"}," + + "{\"address\":{\"x\":\"bar\"},\"values\":\"40A040C040E0\"}" + + "]}"; + var put = createPutWithTensor(inputJson(mixedJson), "mixed_bfloat16_tensor"); + Tensor tensor = assertTensorField(expected, put, "mixed_bfloat16_tensor"); + } + + @Test public void testParsingOfMixedTensorOnMixedForm() { Tensor.Builder builder = Tensor.Builder.of(TensorType.fromSpec("tensor(x{},y[3])")); builder.cell().label("x", 0).label("y", 0).value(2.0); diff --git a/documentapi/src/vespa/documentapi/messagebus/policies/externslobrokpolicy.cpp b/documentapi/src/vespa/documentapi/messagebus/policies/externslobrokpolicy.cpp index 9eb28432234..d12a46ed3a4 100644 --- a/documentapi/src/vespa/documentapi/messagebus/policies/externslobrokpolicy.cpp +++ b/documentapi/src/vespa/documentapi/messagebus/policies/externslobrokpolicy.cpp @@ -55,10 +55,11 @@ ExternSlobrokPolicy::~ExternSlobrokPolicy() } } -string ExternSlobrokPolicy::init() { +string +ExternSlobrokPolicy::init() { if (_slobroks.size() != 0) { slobrok::ConfiguratorFactory config(_slobroks); - _mirror.reset(new MirrorAPI(*_orb, config)); + _mirror = std::make_unique<MirrorAPI>(*_orb, config); } else if (_configSources.size() != 0) { slobrok::ConfiguratorFactory config( config::ConfigUri(_slobrokConfigId, diff --git a/eval/src/tests/tensor/onnx_wrapper/onnx_wrapper_test.cpp b/eval/src/tests/tensor/onnx_wrapper/onnx_wrapper_test.cpp index 9b44dd7519e..54f958f8111 100644 --- a/eval/src/tests/tensor/onnx_wrapper/onnx_wrapper_test.cpp +++ b/eval/src/tests/tensor/onnx_wrapper/onnx_wrapper_test.cpp @@ -436,4 +436,11 @@ TEST(OnnxTest, inspect_float_to_int8_conversion) { //------------------------------------------------------------------------- } +TEST(OnnxTest, default_allocator_type) { + Ort::AllocatorWithDefaultOptions default_alloc; + OrtAllocatorType res = Invalid; + Ort::ThrowOnError(Ort::GetApi().MemoryInfoGetType(default_alloc.GetInfo(), &res)); + fprintf(stderr, "default allocator type: %d\n", int(res)); +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/eval/src/vespa/eval/onnx/onnx_wrapper.cpp b/eval/src/vespa/eval/onnx/onnx_wrapper.cpp index 3a593f491d8..f848c421c9d 100644 --- a/eval/src/vespa/eval/onnx/onnx_wrapper.cpp +++ b/eval/src/vespa/eval/onnx/onnx_wrapper.cpp @@ -414,7 +414,7 @@ struct Onnx::EvalContext::SelectConvertResult { Onnx::EvalContext::EvalContext(const Onnx &model, const WireInfo &wire_info) : _model(model), _wire_info(wire_info), - _cpu_memory(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)), + _cpu_memory(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeDefault)), _param_values(), _result_values(), _results(), @@ -535,6 +535,7 @@ Onnx::Onnx(const vespalib::string &model_file, Optimize optimize) _options.SetIntraOpNumThreads(1); _options.SetInterOpNumThreads(1); _options.SetGraphOptimizationLevel(convert_optimize(optimize)); + _options.DisableCpuMemArena(); _session = Ort::Session(_shared.env(), model_file.c_str(), _options); extract_meta_data(); } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java index 7c46c6e2873..0ce69c182ce 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java @@ -131,9 +131,9 @@ public class FileReferenceDownloader { return false; } } else { - log.log(logLevel, () -> "Request failed. Req: " + request + "\nSpec: " + connection.getAddress() + - ", error code: " + request.errorCode() + ", will use another spec for next request" + - ", retry count " + retryCount + ", rpc timeout " + rpcTimeout.getSeconds()); + log.log(logLevel, () -> "Downloading file " + fileReference + " from " + connection.getAddress() + " failed: " + + request + ", error: " + request.errorMessage() + ", will use another config server for next request" + + " (retry count " + retryCount + ", rpc timeout " + rpcTimeout.getSeconds() + ")"); connectionPool.setError(connection, request.errorCode()); return false; } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index e2a86a3755c..dff6833c015 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -152,7 +152,7 @@ public class Flags { public static final UnboundBooleanFlag GROUP_SUSPENSION = defineFeatureFlag( "group-suspension", true, - List.of("hakon"), "2021-01-22", "2021-05-22", + List.of("hakon"), "2021-01-22", "2021-06-22", "Allow all content nodes in a hierarchical group to suspend at the same time", "Takes effect on the next suspension request to the Orchestrator.", APPLICATION_ID); @@ -184,7 +184,7 @@ public class Flags { CLUSTER_TYPE); public static final UnboundStringFlag DEDICATED_CLUSTER_CONTROLLER_FLAVOR = defineStringFlag( - "dedicated-cluster-controller-flavor", "", List.of("jonmv"), "2021-02-25", "2021-05-25", + "dedicated-cluster-controller-flavor", "", List.of("jonmv"), "2021-02-25", "2021-08-25", "Flavor as <vpu>-<memgb>-<diskgb> to use for dedicated cluster controller nodes", "Takes effect immediately, for subsequent provisioning", APPLICATION_ID); @@ -210,13 +210,6 @@ public class Flags { "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); - public static final UnboundBooleanFlag ENABLE_JDISC_HTTP2 = defineFeatureFlag( - "enable-jdisc-http2", false, - List.of("bjorncs", "jonmv"), "2021-04-12", "2021-08-01", - "Whether jdisc HTTPS connectors should allow HTTP/2", - "Takes effect at redeployment", - APPLICATION_ID); - public static final UnboundBooleanFlag ENABLE_CUSTOM_ACL_MAPPING = defineFeatureFlag( "enable-custom-acl-mapping", false, List.of("mortent","bjorncs"), "2021-04-13", "2021-08-01", @@ -231,6 +224,13 @@ public class Flags { "Takes effect after distributor restart", ZONE_ID, APPLICATION_ID); + public static final UnboundBooleanFlag USE_EXTERNAL_RANK_EXPRESSION = defineFeatureFlag( + "use-external-rank-expression", false, + List.of("baldersheim"), "2021-05-24", "2021-07-01", + "Whether to use distributed external rank expression or inline in rankproperties", + "Takes effect on next internal redeployment", + APPLICATION_ID); + public static final UnboundBooleanFlag ENABLE_ROUTING_CORE_DUMP = defineFeatureFlag( "enable-routing-core-dumps", false, List.of("tokle"), "2021-04-16", "2021-08-01", @@ -241,10 +241,17 @@ public class Flags { public static final UnboundBooleanFlag CFG_DEPLOY_MULTIPART = defineFeatureFlag( "cfg-deploy-multipart", false, List.of("tokle"), "2021-05-19", "2021-08-01", - "Wheter to deploy applications using multipart form data (instead of url params)", + "Whether to deploy applications using multipart form data (instead of url params)", "Takes effect immediately", APPLICATION_ID); + public static final UnboundBooleanFlag VESPA_APP_DOMAIN_IN_CERTIFICATE = defineFeatureFlag( + "new-domain-in-certificate", false, + List.of("mpolden"), "2021-05-25", "2021-09-01", + "Whether to include the vespa-app.cloud names in certificate requests", + "Takes effect on next deployment through controller", + APPLICATION_ID); + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners, String createdAt, String expiresAt, String description, diff --git a/model-evaluation/abi-spec.json b/model-evaluation/abi-spec.json index 63882525808..fac4baf4682 100644 --- a/model-evaluation/abi-spec.json +++ b/model-evaluation/abi-spec.json @@ -67,6 +67,7 @@ "public" ], "methods": [ + "public void <init>(com.yahoo.vespa.config.search.RankProfilesConfig, com.yahoo.vespa.config.search.core.RankingConstantsConfig, com.yahoo.vespa.config.search.core.RankingExpressionsConfig, com.yahoo.vespa.config.search.core.OnnxModelsConfig, com.yahoo.filedistribution.fileacquirer.FileAcquirer)", "public void <init>(com.yahoo.vespa.config.search.RankProfilesConfig, com.yahoo.vespa.config.search.core.RankingConstantsConfig, com.yahoo.vespa.config.search.core.OnnxModelsConfig, com.yahoo.filedistribution.fileacquirer.FileAcquirer)", "public void <init>(java.util.Map)", "public java.util.Map models()", @@ -83,6 +84,7 @@ ], "methods": [ "public void <init>(com.yahoo.filedistribution.fileacquirer.FileAcquirer)", + "public java.util.Map importFrom(com.yahoo.vespa.config.search.RankProfilesConfig, com.yahoo.vespa.config.search.core.RankingConstantsConfig, com.yahoo.vespa.config.search.core.RankingExpressionsConfig, com.yahoo.vespa.config.search.core.OnnxModelsConfig)", "public java.util.Map importFrom(com.yahoo.vespa.config.search.RankProfilesConfig, com.yahoo.vespa.config.search.core.RankingConstantsConfig, com.yahoo.vespa.config.search.core.OnnxModelsConfig)", "protected com.yahoo.tensor.Tensor readTensorFromFile(java.lang.String, com.yahoo.tensor.TensorType, com.yahoo.config.FileReference)" ], diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java index 88766da67fc..2d1a0d069a8 100644 --- a/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -9,9 +9,9 @@ import com.yahoo.filedistribution.fileacquirer.FileAcquirer; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import java.util.Map; -import java.util.stream.Collectors; /** * Evaluates machine-learned models added to Vespa applications and available as config form. @@ -28,9 +28,19 @@ public class ModelsEvaluator extends AbstractComponent { @Inject public ModelsEvaluator(RankProfilesConfig config, RankingConstantsConfig constantsConfig, + RankingExpressionsConfig expressionsConfig, OnnxModelsConfig onnxModelsConfig, FileAcquirer fileAcquirer) { - this(new RankProfilesConfigImporter(fileAcquirer).importFrom(config, constantsConfig, onnxModelsConfig)); + this(new RankProfilesConfigImporter(fileAcquirer) + .importFrom(config, constantsConfig, expressionsConfig, onnxModelsConfig)); + } + + public ModelsEvaluator(RankProfilesConfig config, + RankingConstantsConfig constantsConfig, + OnnxModelsConfig onnxModelsConfig, + FileAcquirer fileAcquirer) { + this(new RankProfilesConfigImporter(fileAcquirer) + .importFrom(config, constantsConfig, new RankingExpressionsConfig.Builder().build(), onnxModelsConfig)); } public ModelsEvaluator(Map<String, Model> models) { diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java index 1bdb2810ddf..06ca7a60f4c 100644 --- a/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -15,6 +15,7 @@ import com.yahoo.tensor.serialization.TypedBinaryFormat; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import java.io.File; import java.io.IOException; @@ -51,11 +52,12 @@ public class RankProfilesConfigImporter { */ public Map<String, Model> importFrom(RankProfilesConfig config, RankingConstantsConfig constantsConfig, + RankingExpressionsConfig expressionsConfig, OnnxModelsConfig onnxModelsConfig) { try { Map<String, Model> models = new HashMap<>(); for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { - Model model = importProfile(profile, constantsConfig, onnxModelsConfig); + Model model = importProfile(profile, constantsConfig, expressionsConfig, onnxModelsConfig); models.put(model.name(), model); } return models; @@ -65,8 +67,16 @@ public class RankProfilesConfigImporter { } } + @Deprecated + public Map<String, Model> importFrom(RankProfilesConfig config, + RankingConstantsConfig constantsConfig, + OnnxModelsConfig onnxModelsConfig) { + return importFrom(config, constantsConfig, new RankingExpressionsConfig.Builder().build(), onnxModelsConfig); + } + private Model importProfile(RankProfilesConfig.Rankprofile profile, RankingConstantsConfig constantsConfig, + RankingExpressionsConfig expressionsConfig, OnnxModelsConfig onnxModelsConfig) throws ParseException { diff --git a/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelTester.java b/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelTester.java index d252594e729..b6878f4ea1a 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelTester.java +++ b/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelTester.java @@ -16,6 +16,7 @@ import com.yahoo.tensor.serialization.TypedBinaryFormat; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import java.io.IOException; import java.util.Map; @@ -46,10 +47,12 @@ public class ModelTester { RankProfilesConfig.class).getConfig(""); RankingConstantsConfig constantsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-constants.cfg").toFile()), RankingConstantsConfig.class).getConfig(""); + RankingExpressionsConfig expresionsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-expressions.cfg").toFile()), + RankingExpressionsConfig.class).getConfig(""); OnnxModelsConfig onnxModelsConfig = new ConfigGetter<>(new FileSource(configDir.append("onnx-models.cfg").toFile()), - OnnxModelsConfig.class).getConfig(""); + OnnxModelsConfig.class).getConfig(""); return new RankProfilesConfigImporterWithMockedConstants(Path.fromString(path).append("constants"), MockFileAcquirer.returnFile(null)) - .importFrom(config, constantsConfig, onnxModelsConfig); + .importFrom(config, constantsConfig, expresionsConfig, onnxModelsConfig); } public ExpressionFunction assertFunction(String name, String expression, Model model) { diff --git a/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java index dce033c79b0..2a8b7479b00 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -12,6 +12,7 @@ import com.yahoo.tensor.TensorType; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import com.yahoo.yolean.Exceptions; import org.junit.Test; @@ -132,9 +133,11 @@ public class ModelsEvaluatorTest { RankProfilesConfig.class).getConfig(""); RankingConstantsConfig constantsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-constants.cfg").toFile()), RankingConstantsConfig.class).getConfig(""); + RankingExpressionsConfig expressionsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-expressions.cfg").toFile()), + RankingExpressionsConfig.class).getConfig(""); OnnxModelsConfig onnxModelsConfig = new ConfigGetter<>(new FileSource(configDir.append("onnx-models.cfg").toFile()), OnnxModelsConfig.class).getConfig(""); - return new ModelsEvaluator(config, constantsConfig, onnxModelsConfig, MockFileAcquirer.returnFile(null)); + return new ModelsEvaluator(config, constantsConfig, expressionsConfig, onnxModelsConfig, MockFileAcquirer.returnFile(null)); } } diff --git a/model-evaluation/src/test/java/ai/vespa/models/evaluation/OnnxEvaluatorTest.java b/model-evaluation/src/test/java/ai/vespa/models/evaluation/OnnxEvaluatorTest.java index 1d55fdf9e6a..c137fb9a522 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/evaluation/OnnxEvaluatorTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/evaluation/OnnxEvaluatorTest.java @@ -10,6 +10,7 @@ import com.yahoo.tensor.Tensor; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import org.junit.Test; import java.io.File; @@ -54,6 +55,8 @@ public class OnnxEvaluatorTest { RankProfilesConfig.class).getConfig(""); RankingConstantsConfig constantsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-constants.cfg").toFile()), RankingConstantsConfig.class).getConfig(""); + RankingExpressionsConfig expressionsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-expressions.cfg").toFile()), + RankingExpressionsConfig.class).getConfig(""); OnnxModelsConfig onnxModelsConfig = new ConfigGetter<>(new FileSource(configDir.append("onnx-models.cfg").toFile()), OnnxModelsConfig.class).getConfig(""); @@ -63,7 +66,7 @@ public class OnnxEvaluatorTest { } FileAcquirer fileAcquirer = MockFileAcquirer.returnFiles(fileMap); - return new ModelsEvaluator(config, constantsConfig, onnxModelsConfig, fileAcquirer); + return new ModelsEvaluator(config, constantsConfig, expressionsConfig, onnxModelsConfig, fileAcquirer); } } diff --git a/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java b/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java index a69a220e532..d5282bd52f7 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java @@ -12,6 +12,7 @@ import com.yahoo.tensor.TensorType; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import org.junit.BeforeClass; import org.junit.Test; @@ -198,12 +199,14 @@ public class ModelsEvaluationHandlerTest { RankProfilesConfig.class).getConfig(""); RankingConstantsConfig constantsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-constants.cfg").toFile()), RankingConstantsConfig.class).getConfig(""); + RankingExpressionsConfig expressionsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-expressions.cfg").toFile()), + RankingExpressionsConfig.class).getConfig(""); OnnxModelsConfig onnxModelsConfig = new ConfigGetter<>(new FileSource(configDir.append("onnx-models.cfg").toFile()), OnnxModelsConfig.class).getConfig(""); ModelTester.RankProfilesConfigImporterWithMockedConstants importer = new ModelTester.RankProfilesConfigImporterWithMockedConstants(Path.fromString(path).append("constants"), MockFileAcquirer.returnFile(null)); - return new ModelsEvaluator(importer.importFrom(config, constantsConfig, onnxModelsConfig)); + return new ModelsEvaluator(importer.importFrom(config, constantsConfig, expressionsConfig, onnxModelsConfig)); } private String inputTensor() { diff --git a/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java b/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java index 6cfda4d8ce8..97692de56ef 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java @@ -11,6 +11,7 @@ import com.yahoo.tensor.Tensor; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; +import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import org.junit.BeforeClass; import org.junit.Test; @@ -122,6 +123,8 @@ public class OnnxEvaluationHandlerTest { RankProfilesConfig.class).getConfig(""); RankingConstantsConfig constantsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-constants.cfg").toFile()), RankingConstantsConfig.class).getConfig(""); + RankingExpressionsConfig expressionsConfig = new ConfigGetter<>(new FileSource(configDir.append("ranking-expressions.cfg").toFile()), + RankingExpressionsConfig.class).getConfig(""); OnnxModelsConfig onnxModelsConfig = new ConfigGetter<>(new FileSource(configDir.append("onnx-models.cfg").toFile()), OnnxModelsConfig.class).getConfig(""); @@ -131,7 +134,7 @@ public class OnnxEvaluationHandlerTest { } FileAcquirer fileAcquirer = MockFileAcquirer.returnFiles(fileMap); - return new ModelsEvaluator(config, constantsConfig, onnxModelsConfig, fileAcquirer); + return new ModelsEvaluator(config, constantsConfig, expressionsConfig, onnxModelsConfig, fileAcquirer); } } diff --git a/model-evaluation/src/test/resources/config/models/ranking-expressions.cfg b/model-evaluation/src/test/resources/config/models/ranking-expressions.cfg new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/model-evaluation/src/test/resources/config/models/ranking-expressions.cfg diff --git a/model-evaluation/src/test/resources/config/onnx/ranking-expressions.cfg b/model-evaluation/src/test/resources/config/onnx/ranking-expressions.cfg new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/model-evaluation/src/test/resources/config/onnx/ranking-expressions.cfg diff --git a/model-evaluation/src/test/resources/config/rankexpression/ranking-expressions.cfg b/model-evaluation/src/test/resources/config/rankexpression/ranking-expressions.cfg new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/model-evaluation/src/test/resources/config/rankexpression/ranking-expressions.cfg diff --git a/model-evaluation/src/test/resources/config/smallconstant/ranking-expressions.cfg b/model-evaluation/src/test/resources/config/smallconstant/ranking-expressions.cfg new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/model-evaluation/src/test/resources/config/smallconstant/ranking-expressions.cfg diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TestProcessFactory.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TestProcessFactory.java index 0586797d259..6771e0d5a27 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TestProcessFactory.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TestProcessFactory.java @@ -23,6 +23,8 @@ public class TestProcessFactory implements ProcessFactory { private final List<SpawnCall> expectedSpawnCalls = new ArrayList<>(); private final List<CommandLine> spawnCommandLines = new ArrayList<>(); + private boolean muteVerifyAllCommandsExecuted = false; + /** Forward call to spawn() to callback. */ public TestProcessFactory interceptSpawn(String commandDescription, Function<CommandLine, ChildProcess2> callback) { @@ -32,9 +34,10 @@ public class TestProcessFactory implements ProcessFactory { // Convenience method for the caller to avoid having to create a TestChildProcess2 instance. public TestProcessFactory expectSpawn(String commandLineString, TestChildProcess2 toReturn) { + int commandIndex = expectedSpawnCalls.size(); return interceptSpawn( commandLineString, - commandLine -> defaultSpawn(commandLine, commandLineString, toReturn)); + commandLine -> defaultSpawn(commandLine, commandLineString, toReturn, commandIndex)); } // Convenience method for the caller to avoid having to create a TestChildProcess2 instance. @@ -54,6 +57,8 @@ public class TestProcessFactory implements ProcessFactory { } public void verifyAllCommandsExecuted() { + if (muteVerifyAllCommandsExecuted) return; + if (spawnCommandLines.size() < expectedSpawnCalls.size()) { int missingCommandIndex = spawnCommandLines.size(); throw new IllegalStateException("Command #" + missingCommandIndex + @@ -81,12 +86,14 @@ public class TestProcessFactory implements ProcessFactory { return expectedSpawnCalls.get(spawnCommandLines.size() - 1).callback.apply(commandLine); } - private static ChildProcess2 defaultSpawn(CommandLine commandLine, + private ChildProcess2 defaultSpawn(CommandLine commandLine, String expectedCommandLineString, - ChildProcess2 toReturn) { + ChildProcess2 toReturn, + int commandSequenceNumber) { String actualCommandLineString = commandLine.toString(); if (!Objects.equals(actualCommandLineString, expectedCommandLineString)) { - throw new IllegalArgumentException("Expected command line '" + + muteVerifyAllCommandsExecuted = true; + throw new IllegalArgumentException("Expected command #" + commandSequenceNumber + " to be '" + expectedCommandLineString + "' but got '" + actualCommandLineString + "'"); } diff --git a/node-repository/pom.xml b/node-repository/pom.xml index 05220b6977f..d2deaf51afe 100644 --- a/node-repository/pom.xml +++ b/node-repository/pom.xml @@ -75,7 +75,7 @@ <dependency> <groupId>org.questdb</groupId> <artifactId>questdb</artifactId> - <version>5.0.6</version> + <version>6.0.2</version> <scope>compile</scope> </dependency> <dependency> diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java index 79b09348d21..0a1c6c5df6b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java @@ -49,7 +49,6 @@ public class AllocationOptimizer { new ClusterResources(maximumNodes, maximumNodes, NodeResources.unspecified())); else limits = atLeast(minimumNodes, limits); - Optional<AllocatableClusterResources> bestAllocation = Optional.empty(); NodeList hosts = nodeRepository.nodes().list().hosts(); for (int groups = limits.min().groups(); groups <= limits.max().groups(); groups++) { @@ -67,8 +66,8 @@ public class AllocationOptimizer { nodeResourcesWith(nodesAdjustedForRedundancy, groupsAdjustedForRedundancy, limits, target, current, clusterModel)); - var allocatableResources = AllocatableClusterResources.from(next, current.clusterSpec(), limits, hosts, nodeRepository); + if (allocatableResources.isEmpty()) continue; if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get())) bestAllocation = allocatableResources; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsDb.java index 5ec1aac7d2d..ba317c0a58e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsDb.java @@ -43,6 +43,9 @@ public interface MetricsDb { /** Returns all cluster level metric snapshots for a given cluster */ ClusterTimeseries getClusterTimeseries(ApplicationId applicationId, ClusterSpec.Id clusterId); + /** Returns the number of times QuestDb has returned null records since last gc */ + default int getNullRecordsCount() { return 0; } + /** Must be called intermittently (as long as add is called) to gc old data */ void gc(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java index f71e7d608e0..2c29b9cc560 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java @@ -59,6 +59,8 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { private long highestTimestampAdded = 0; + private volatile int nullRecords = 0; + @Inject public QuestMetricsDb() { this(Defaults.getDefaults().underVespaHome("var/db/vespa/autoscaling"), Clock.systemUTC()); @@ -80,9 +82,7 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { // silence Questdb's custom logging system IOUtils.writeFile(new File(dataDir, "quest-log.conf"), new byte[0]); - System.setProperty("questdbLog", dataDir + "/quest-log.conf"); - System.setProperty("org.jooq.no-logo", "true"); - + System.setProperty("out", dataDir + "/quest-log.conf"); CairoConfiguration configuration = new DefaultCairoConfiguration(dataDir); engine = new CairoEngine(configuration); sqlCompiler = ThreadLocal.withInitial(() -> new SqlCompiler(engine)); @@ -183,8 +183,11 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { } } + public int getNullRecordsCount() { return nullRecords; } + @Override public void gc() { + nullRecords = 0; gc(nodeTable); gc(clusterTable); } @@ -344,7 +347,7 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.of("UTC")); String from = formatter.format(startTime).substring(0, 19) + ".000000Z"; String to = formatter.format(clock.instant()).substring(0, 19) + ".000000Z"; - String sql = "select * from " + nodeTable + " where at in('" + from + "', '" + to + "');"; + String sql = "select * from " + nodeTable + " where at between('" + from + "', '" + to + "');"; // WHERE clauses does not work: // String sql = "select * from " + tableName + " where hostname in('host1', 'host2', 'host3');"; @@ -354,6 +357,10 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb { try (RecordCursor cursor = factory.getCursor(context)) { Record record = cursor.getRecord(); while (cursor.hasNext()) { + if (record == null || record.getStr(0) == null) { // Observed to happen. QuestDb bug? + nullRecords++; + continue; + } String hostname = record.getStr(0).toString(); if (hostnames.isEmpty() || hostnames.contains(hostname)) { snapshots.put(hostname, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java index 17bd4b9174c..e98da35aa6a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.maintenance; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; @@ -43,26 +42,26 @@ import java.util.stream.Collectors; public class FailedExpirer extends NodeRepositoryMaintainer { private static final Logger log = Logger.getLogger(FailedExpirer.class.getName()); - // We will stop giving the nodes back to Openstack for break-fix, setting this to number a high value, we might - // eventually remove this counter and recycling nodes forever + // Try recycling nodes until reaching this many failures + // TODO: Consider removing this altogether as this effectively always recycles nodes private static final int maxAllowedFailures = 50; private final NodeRepository nodeRepository; - private final Duration defaultExpiry; // Grace period to allow recovery of data - private final Duration containerExpiry; // Stateless nodes, no data to recover + private final Duration statefulExpiry; // Stateful nodes: Grace period to allow recovery of data + private final Duration statelessExpiry; // Stateless nodes: No data to recover FailedExpirer(NodeRepository nodeRepository, Zone zone, Duration interval, Metric metric) { super(nodeRepository, interval, metric); this.nodeRepository = nodeRepository; if (zone.system().isCd()) { - defaultExpiry = containerExpiry = Duration.ofMinutes(30); + statefulExpiry = statelessExpiry = Duration.ofMinutes(30); } else { if (zone.environment() == Environment.staging || zone.environment() == Environment.test) { - defaultExpiry = Duration.ofHours(1); + statefulExpiry = Duration.ofHours(1); } else { - defaultExpiry = Duration.ofDays(4); + statefulExpiry = Duration.ofDays(4); } - containerExpiry = Duration.ofHours(1); + statelessExpiry = Duration.ofHours(1); } } @@ -74,10 +73,11 @@ public class FailedExpirer extends NodeRepositoryMaintainer { recycleIf(remainingNodes, node -> node.allocation().isEmpty()); recycleIf(remainingNodes, node -> - node.allocation().get().membership().cluster().type() == ClusterSpec.Type.container && - node.history().hasEventBefore(History.Event.Type.failed, clock().instant().minus(containerExpiry))); + !node.allocation().get().membership().cluster().isStateful() && + node.history().hasEventBefore(History.Event.Type.failed, clock().instant().minus(statelessExpiry))); recycleIf(remainingNodes, node -> - node.history().hasEventBefore(History.Event.Type.failed, clock().instant().minus(defaultExpiry))); + node.allocation().get().membership().cluster().isStateful() && + node.history().hasEventBefore(History.Event.Type.failed, clock().instant().minus(statefulExpiry))); return true; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java index a2e02c15737..85437b3e78a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java @@ -19,6 +19,7 @@ import com.yahoo.vespa.hosted.provision.Node.State; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Allocation; +import com.yahoo.vespa.hosted.provision.node.ClusterId; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.persistence.CacheStats; import com.yahoo.vespa.orchestrator.Orchestrator; @@ -30,7 +31,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -84,11 +84,11 @@ public class MetricsReporter extends NodeRepositoryMaintainer { } private void updateAllocationMetrics(NodeList nodes) { - Map<ClusterKey, List<Node>> byCluster = nodes.stream() - .filter(node -> node.allocation().isPresent()) - .filter(node -> !node.allocation().get().owner().instance().isTester()) - .collect(Collectors.groupingBy(node -> new ClusterKey(node.allocation().get().owner(), node.allocation().get().membership().cluster().id()))); - byCluster.forEach((clusterKey, allocatedNodes) -> { + Map<ClusterId, List<Node>> byCluster = nodes.stream() + .filter(node -> node.allocation().isPresent()) + .filter(node -> !node.allocation().get().owner().instance().isTester()) + .collect(Collectors.groupingBy(node -> new ClusterId(node.allocation().get().owner(), node.allocation().get().membership().cluster().id()))); + byCluster.forEach((clusterId, allocatedNodes) -> { int activeNodes = 0; int nonActiveNodes = 0; for (var node : allocatedNodes) { @@ -104,7 +104,7 @@ public class MetricsReporter extends NodeRepositoryMaintainer { } else { nonActiveFraction = (double) nonActiveNodes / ((double) activeNodes + (double) nonActiveNodes); } - Metric.Context context = getContext(dimensions(clusterKey.application, clusterKey.cluster)); + Metric.Context context = getContext(dimensions(clusterId.application(), clusterId.cluster())); metric.set("nodes.active", activeNodes, context); metric.set("nodes.nonActive", nonActiveNodes, context); metric.set("nodes.nonActiveFraction", nonActiveFraction, context); @@ -112,16 +112,16 @@ public class MetricsReporter extends NodeRepositoryMaintainer { } private void updateExclusiveSwitchMetrics(NodeList nodes) { - Map<ClusterKey, List<Node>> byCluster = nodes.stream() + Map<ClusterId, List<Node>> byCluster = nodes.stream() .filter(node -> node.type() == NodeType.tenant) .filter(node -> node.state() == State.active) .filter(node -> node.allocation().isPresent()) - .collect(Collectors.groupingBy(node -> new ClusterKey(node.allocation().get().owner(), node.allocation().get().membership().cluster().id()))); - byCluster.forEach((clusterKey, clusterNodes) -> { + .collect(Collectors.groupingBy(node -> new ClusterId(node.allocation().get().owner(), node.allocation().get().membership().cluster().id()))); + byCluster.forEach((clusterId, clusterNodes) -> { NodeList clusterHosts = nodes.parentsOf(NodeList.copyOf(clusterNodes)); long nodesOnExclusiveSwitch = NodeList.copyOf(clusterNodes).onExclusiveSwitch(clusterHosts).size(); double exclusiveSwitchRatio = nodesOnExclusiveSwitch / (double) clusterNodes.size(); - metric.set("nodes.exclusiveSwitchFraction", exclusiveSwitchRatio, getContext(dimensions(clusterKey.application, clusterKey.cluster))); + metric.set("nodes.exclusiveSwitchFraction", exclusiveSwitchRatio, getContext(dimensions(clusterId.application(), clusterId.cluster()))); }); } @@ -390,30 +390,4 @@ public class MetricsReporter extends NodeRepositoryMaintainer { .reduce(host.flavor().resources().justNumbers(), NodeResources::subtract); } - private static class ClusterKey { - - private final ApplicationId application; - private final ClusterSpec.Id cluster; - - public ClusterKey(ApplicationId application, ClusterSpec.Id cluster) { - this.application = application; - this.cluster = cluster; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ClusterKey that = (ClusterKey) o; - return application.equals(that.application) && - cluster.equals(that.cluster); - } - - @Override - public int hashCode() { - return Objects.hash(application, cluster); - } - - } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java index f95094f891c..1ea4577f7fe 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java @@ -6,7 +6,6 @@ import com.yahoo.jdisc.Metric; import com.yahoo.lang.MutableInteger; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.autoscale.MetricsFetcher; -import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb; import com.yahoo.vespa.hosted.provision.autoscale.MetricsResponse; import com.yahoo.yolean.Exceptions; @@ -51,6 +50,10 @@ public class NodeMetricsDbMaintainer extends NodeRepositoryMaintainer { if (++done < applications.size()) Thread.sleep(pauseMs); } + + if (nodeRepository().metricsDb().getNullRecordsCount() > 0) + log.warning(nodeRepository().metricsDb().getNullRecordsCount() + " records returned null"); + nodeRepository().metricsDb().gc(); // Suppress failures for manual zones for now to avoid noise diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/ClusterId.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/ClusterId.java new file mode 100644 index 00000000000..ec9e4dc981b --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/ClusterId.java @@ -0,0 +1,50 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.node; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ClusterSpec; + +import java.util.Objects; + +/** + * Identifies a cluster in an application. + * + * @author mpolden + */ +public class ClusterId { + + private final ApplicationId application; + private final ClusterSpec.Id cluster; + + public ClusterId(ApplicationId application, ClusterSpec.Id cluster) { + this.application = Objects.requireNonNull(application); + this.cluster = Objects.requireNonNull(cluster); + } + + public ApplicationId application() { + return application; + } + + public ClusterSpec.Id cluster() { + return cluster; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterId that = (ClusterId) o; + return application.equals(that.application) && cluster.equals(that.cluster); + } + + @Override + public int hashCode() { + return Objects.hash(application, cluster); + } + + @Override + public String toString() { + return cluster + " of " + application; + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java index 266855002ea..2ef68566a87 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java @@ -520,32 +520,34 @@ public class Nodes { /** * Throws if the given node cannot be removed. Removal is allowed if: - * - Tenant node: node is unallocated + * - Tenant node: + * - non-recursively: node is unallocated + * - recursively: node is unallocated or node is in failed|parked * - Host node: iff in state provisioned|failed|parked * - Child node: - * If only removing the container node: node in state ready - * If also removing the parent node: child is in state provisioned|failed|parked|dirty|ready + * - non-recursively: node in state ready + * - recursively: child is in state provisioned|failed|parked|dirty|ready */ - private void requireRemovable(Node node, boolean removingAsChild, boolean force) { + private void requireRemovable(Node node, boolean removingRecursively, boolean force) { if (force) return; - if (node.type() == NodeType.tenant && node.allocation().isPresent()) - illegal(node + " is currently allocated and cannot be removed"); - - if (!node.type().isHost() && !removingAsChild) { - if (node.state() != Node.State.ready) - illegal(node + " can not be removed as it is not in the state " + Node.State.ready); - } - else if (!node.type().isHost()) { // removing a child node - Set<Node.State> legalStates = EnumSet.of(Node.State.provisioned, Node.State.failed, Node.State.parked, Node.State.dirty, Node.State.ready); - if ( ! legalStates.contains(node.state())) - illegal(node + " can not be removed as it is not in the states " + legalStates); + if (node.type() == NodeType.tenant && node.allocation().isPresent()) { + EnumSet<Node.State> removableStates = EnumSet.of(Node.State.failed, Node.State.parked); + if (!removingRecursively || !removableStates.contains(node.state())) + illegal(node + " is currently allocated and cannot be removed while in " + node.state()); } - else { // a host - Set<Node.State> legalStates = EnumSet.of(Node.State.provisioned, Node.State.failed, Node.State.parked); - if (! legalStates.contains(node.state())) - illegal(node + " can not be removed as it is not in the states " + legalStates); + + final Set<Node.State> removableStates; + if (node.type().isHost()) { + removableStates = EnumSet.of(Node.State.provisioned, Node.State.failed, Node.State.parked); + } else { + removableStates = removingRecursively + ? EnumSet.of(Node.State.provisioned, Node.State.failed, Node.State.parked, Node.State.dirty, Node.State.ready) + // When not removing recursively, we can only remove children in state ready + : EnumSet.of(Node.State.ready); } + if (!removableStates.contains(node.state())) + illegal(node + " can not be removed while in " + node.state()); } /** diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java index fce9eb562c9..20536b9dd9f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java @@ -2,8 +2,6 @@ package com.yahoo.vespa.hosted.provision.os; import com.yahoo.component.Version; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.flags.IntFlag; import com.yahoo.vespa.flags.PermanentFlags; @@ -12,6 +10,7 @@ import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Allocation; +import com.yahoo.vespa.hosted.provision.node.ClusterId; import com.yahoo.vespa.hosted.provision.node.filter.NodeListFilter; import java.time.Instant; @@ -19,7 +18,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; @@ -59,36 +57,37 @@ public class RebuildingOsUpgrader implements OsUpgrader { } /** Returns the number of hosts of given type that can be rebuilt concurrently */ - private int upgradeLimit(NodeType hostType, NodeList hosts) { + private int rebuildLimit(NodeType hostType, NodeList hostsOfType) { + if (hostsOfType.stream().anyMatch(host -> host.type() != hostType)) illegal("All hosts must be a " + hostType); int limit = hostType == NodeType.host ? maxRebuilds.value() : 1; - return Math.max(0, limit - hosts.rebuilding().size()); + return Math.max(0, limit - hostsOfType.rebuilding().size()); } private List<Node> rebuildableHosts(OsVersionTarget target, NodeList allNodes) { NodeList hostsOfTargetType = allNodes.nodeType(target.nodeType()); - NodeList activeHosts = hostsOfTargetType.state(Node.State.active); - int upgradeLimit = upgradeLimit(target.nodeType(), hostsOfTargetType); + int rebuildLimit = rebuildLimit(target.nodeType(), hostsOfTargetType); // Find stateful clusters with retiring nodes NodeList activeNodes = allNodes.state(Node.State.active); Set<ClusterId> retiringClusters = statefulClustersOf(activeNodes.nodeType(target.nodeType().childNodeType()) .retiring()); - // Upgrade hosts not running stateful clusters that are already retiring - List<Node> hostsToUpgrade = new ArrayList<>(upgradeLimit); - NodeList candidates = activeHosts.not().rebuilding() - .osVersionIsBefore(target.version()) - .byIncreasingOsVersion(); + // Rebuild hosts not containing stateful clusters with retiring nodes, up to rebuild limit + List<Node> hostsToRebuild = new ArrayList<>(rebuildLimit); + NodeList candidates = hostsOfTargetType.state(Node.State.active) + .not().rebuilding() + .osVersionIsBefore(target.version()) + .byIncreasingOsVersion(); for (Node host : candidates) { - if (hostsToUpgrade.size() == upgradeLimit) break; + if (hostsToRebuild.size() == rebuildLimit) break; Set<ClusterId> clustersOnHost = statefulClustersOf(activeNodes.childrenOf(host)); - boolean canUpgrade = Collections.disjoint(retiringClusters, clustersOnHost); - if (canUpgrade) { - hostsToUpgrade.add(host); + boolean canRebuild = Collections.disjoint(retiringClusters, clustersOnHost); + if (canRebuild) { + hostsToRebuild.add(host); retiringClusters.addAll(clustersOnHost); } } - return Collections.unmodifiableList(hostsToUpgrade); + return Collections.unmodifiableList(hostsToRebuild); } private void rebuild(Node host, Version target, Instant now) { @@ -102,7 +101,7 @@ public class RebuildingOsUpgrader implements OsUpgrader { private static Set<ClusterId> statefulClustersOf(NodeList nodes) { Set<ClusterId> clusters = new HashSet<>(); for (Node node : nodes) { - if (node.type().isHost()) throw new IllegalArgumentException("All nodes must be children, got host " + node); + if (node.type().isHost()) illegal("All nodes must be children, got host " + node); if (node.allocation().isEmpty()) continue; Allocation allocation = node.allocation().get(); if (!allocation.membership().cluster().isStateful()) continue; @@ -111,34 +110,8 @@ public class RebuildingOsUpgrader implements OsUpgrader { return clusters; } - private static class ClusterId { - - private final ApplicationId application; - private final ClusterSpec.Id cluster; - - public ClusterId(ApplicationId application, ClusterSpec.Id cluster) { - this.application = Objects.requireNonNull(application); - this.cluster = Objects.requireNonNull(cluster); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ClusterId that = (ClusterId) o; - return application.equals(that.application) && cluster.equals(that.cluster); - } - - @Override - public int hashCode() { - return Objects.hash(application, cluster); - } - - @Override - public String toString() { - return cluster + " of " + application; - } - + private static void illegal(String msg) { + throw new IllegalArgumentException(msg); } } 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 81a56e4d47e..def992a264b 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 @@ -62,8 +62,8 @@ public class GroupPreparer { List<Node> surplusActiveNodes, NodeIndices indices, int wantedGroups) { String allocateOsRequirement = allocateOsRequirementFlag - .with(FetchVector.Dimension.APPLICATION_ID, application.serializedForm()) - .value(); + .with(FetchVector.Dimension.APPLICATION_ID, application.serializedForm()) + .value(); // Try preparing in memory without global unallocated lock. Most of the time there should be no changes and we // can return nodes previously allocated. @@ -89,24 +89,16 @@ public class GroupPreparer { allocateOsRequirement); NodeType hostType = allocation.nodeType().hostType(); if (canProvisionDynamically(hostType)) { - final Version osVersion; - if (allocateOsRequirement.equals("rhel8")) { - osVersion = new Version(8, Integer.MAX_VALUE /* always use latest 8 version */, 0); - } else if (allocateOsRequirement.equals("rhel7")) { - osVersion = new Version(7, Integer.MAX_VALUE /* always use latest 7 version */, 0); - } else { - osVersion = nodeRepository.osVersions().targetFor(hostType).orElse(Version.emptyVersion); - } HostSharing sharing = hostSharing(requestedNodes, hostType); List<ProvisionedHost> provisionedHosts = allocation.hostDeficit() - .map(deficit -> { - return hostProvisioner.get().provisionHosts(allocation.provisionIndices(deficit.count()), - hostType, - deficit.resources(), - application, - osVersion, - sharing); - }) + .map(deficit -> + hostProvisioner.get().provisionHosts(allocation.provisionIndices(deficit.count()), + hostType, + deficit.resources(), + application, + decideOsVersion(allocateOsRequirement, hostType), + sharing) + ) .orElseGet(List::of); // At this point we have started provisioning of the hosts, the first priority is to make sure that @@ -141,12 +133,17 @@ public class GroupPreparer { List<Node> surplusActiveNodes, Supplier<Integer> nextIndex, int wantedGroups, Mutex allocationLock, String allocateOsRequirement) { LockedNodeList allNodes = nodeRepository.nodes().list(allocationLock); - NodeAllocation allocation = new NodeAllocation(allNodes, application, cluster, requestedNodes, - nextIndex, nodeRepository); - NodePrioritizer prioritizer = new NodePrioritizer( - allNodes, application, cluster, requestedNodes, wantedGroups, - nodeRepository.zone().getCloud().dynamicProvisioning(), nodeRepository.nameResolver(), - nodeRepository.resourcesCalculator(), nodeRepository.spareCount(), allocateOsRequirement); + NodeAllocation allocation = new NodeAllocation(allNodes, application, cluster, requestedNodes, nextIndex, nodeRepository); + NodePrioritizer prioritizer = new NodePrioritizer(allNodes, + application, + cluster, + requestedNodes, + wantedGroups, + nodeRepository.zone().getCloud().dynamicProvisioning(), + nodeRepository.nameResolver(), + nodeRepository.resourcesCalculator(), + nodeRepository.spareCount(), + allocateOsRequirement); allocation.offer(prioritizer.collect(surplusActiveNodes)); return allocation; } @@ -164,4 +161,13 @@ public class GroupPreparer { return sharing; } + private Version decideOsVersion(String allocateOsRequirement, NodeType hostType) { + if (allocateOsRequirement.equals("rhel8")) + return new Version(8, Integer.MAX_VALUE /* always use latest 8 version */, 0); + else if (allocateOsRequirement.equals("rhel7")) + return new Version(7, Integer.MAX_VALUE /* always use latest 7 version */, 0); + else + return nodeRepository.osVersions().targetFor(hostType).orElse(Version.emptyVersion); + } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index ab881a68ebe..ee8ce23a5c0 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.ApplicationTransaction; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.HostFilter; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.NodeResources; @@ -27,7 +28,6 @@ import com.yahoo.vespa.hosted.provision.autoscale.AllocatableClusterResources; import com.yahoo.vespa.hosted.provision.autoscale.AllocationOptimizer; import com.yahoo.vespa.hosted.provision.autoscale.ClusterModel; import com.yahoo.vespa.hosted.provision.autoscale.Limits; -import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb; import com.yahoo.vespa.hosted.provision.autoscale.ResourceTarget; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; @@ -184,7 +184,7 @@ public class NodeRepositoryProvisioner implements Provisioner { current, clusterModel, limits) - .orElseThrow(() -> new IllegalArgumentException("No allocation possible within " + limits)) + .orElseThrow(() -> newNoAllocationPossible(current.clusterSpec(), limits)) .advertisedResources(); } @@ -224,4 +224,37 @@ public class NodeRepositoryProvisioner implements Provisioner { } } + private IllegalArgumentException newNoAllocationPossible(ClusterSpec spec, Limits limits) { + StringBuilder message = new StringBuilder("No allocation possible within ").append(limits); + + boolean exclusiveHosts = spec.isExclusive() || nodeRepository.zone().getCloud().dynamicProvisioning(); + if (exclusiveHosts) + message.append(". Nearest allowed node resources: ").append(findNearestNodeResources(limits)); + + return new IllegalArgumentException(message.toString()); + } + + private NodeResources findNearestNodeResources(Limits limits) { + NodeResources nearestMin = nearestFlavorResources(limits.min().nodeResources()); + NodeResources nearestMax = nearestFlavorResources(limits.max().nodeResources()); + if (limits.min().nodeResources().distanceTo(nearestMin) < limits.max().nodeResources().distanceTo(nearestMax)) + return nearestMin; + else + return nearestMax; + } + + /** Returns the advertised flavor resources which are nearest to the given resources */ + private NodeResources nearestFlavorResources(NodeResources requestedResources) { + NodeResources nearestHostResources = nodeRepository.flavors().getFlavors().stream() + .map(flavor -> nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor)) + .filter(resources -> resources.diskSpeed().compatibleWith(requestedResources.diskSpeed())) + .filter(resources -> resources.storageType().compatibleWith(requestedResources.storageType())) + .min(Comparator.comparingDouble(resources -> resources.distanceTo(requestedResources))) + .orElseThrow() + .withBandwidthGbps(requestedResources.bandwidthGbps()); + if ( nearestHostResources.storageType() == NodeResources.StorageType.remote) + nearestHostResources = nearestHostResources.withDiskGb(requestedResources.diskGb()); + return nearestHostResources; + } + } 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 d2b701e5312..97f935d273b 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 @@ -45,8 +45,7 @@ class Preparer { catch (OutOfCapacityException e) { throw new OutOfCapacityException("Could not satisfy " + requestedNodes + ( wantedGroups > 1 ? " (in " + wantedGroups + " groups)" : "") + - " in " + application + " " + cluster + - ": " + e.getMessage()); + " in " + application + " " + cluster + ": " + e.getMessage()); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java index b3ee30e4258..d20d6dd1dc4 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.provision.os; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.NodeResources; @@ -24,7 +23,6 @@ import java.time.temporal.ChronoUnit; import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -435,18 +433,11 @@ public class OsVersionsTest { } @Test - public void upgrade_by_rebuilding_is_limited_by_infrastructure_host() { + public void upgrade_by_rebuilding_limits_infrastructure_host() { int hostCount = 3; tester.flagSource().withIntFlag(PermanentFlags.MAX_REBUILDS.id(), hostCount); var versions = new OsVersions(tester.nodeRepository(), false, Integer.MAX_VALUE); - ApplicationId routingApp = ApplicationId.from("t1", "a1", "i1"); - List<Node> proxyHosts = provisionInfraApplication(hostCount, infraApplication, NodeType.proxyhost); - for (int i = 0; i < proxyHosts.size(); i++) { - tester.makeReadyChildren(1, i, new NodeResources(4,8, 100, 0.3), - NodeType.proxy, proxyHosts.get(i).hostname(), (index) -> "proxy" + index); - } - Capacity capacity = Capacity.fromRequiredNodeType(NodeType.proxy); - tester.deploy(routingApp, capacity); + provisionInfraApplication(hostCount, infraApplication, NodeType.proxyhost); Supplier<NodeList> hosts = () -> tester.nodeRepository().nodes().list().nodeType(NodeType.proxyhost); // All hosts are on initial version @@ -458,12 +449,11 @@ public class OsVersionsTest { var version1 = Version.fromString("8.0"); versions.setTarget(NodeType.proxyhost, version1, Duration.ZERO, false); - // Upgrades 1 host at a time + // Upgrades 1 infrastructure host at a time for (int i = 0; i < hostCount; i++) { versions.resumeUpgradeOf(NodeType.proxyhost, true); List<Node> hostsRebuilding = hosts.get().rebuilding().asList(); assertEquals(1, hostsRebuilding.size()); - replaceNodes(routingApp, (app) -> tester.deploy(app, capacity)); completeRebuildOf(hostsRebuilding, NodeType.proxyhost); } } @@ -475,18 +465,14 @@ public class OsVersionsTest { } private void replaceNodes(ApplicationId application) { - replaceNodes(application, this::deployApplication); - } - - private void replaceNodes(ApplicationId application, Consumer<ApplicationId> deployer) { // Deploy to retire nodes - deployer.accept(application); + deployApplication(application); List<Node> retired = tester.nodeRepository().nodes().list().owner(application).retired().asList(); assertFalse("At least one node is retired", retired.isEmpty()); tester.nodeRepository().nodes().setRemovable(application, retired); // Redeploy to deactivate removable nodes and allocate new ones - deployer.accept(application); + deployApplication(application); tester.nodeRepository().nodes().list(Node.State.inactive).owner(application) .forEach(node -> tester.nodeRepository().nodes().removeRecursively(node, true)); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java index 3ad9041cdbb..f2ca993f4d7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java @@ -338,7 +338,8 @@ public class DockerProvisioningTest { tester.makeReadyHosts(2, hostFlavor.resources()).activateTenantHosts(); ApplicationId app1 = ProvisioningTester.applicationId("app1"); - ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); + ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, + new ClusterSpec.Id("cluster1")).vespaVersion("7").build(); // 5 Gb requested memory becomes 5-3=2 Gb real memory, which is an illegally small amount var resources = new NodeResources(1, 5, 10, 1); @@ -346,7 +347,34 @@ public class DockerProvisioningTest { new ClusterResources(4, 1, resources))); } catch (IllegalArgumentException e) { - assertEquals("No allocation possible within limits: from 2 nodes with [vcpu: 1.0, memory: 5.0 Gb, disk 10.0 Gb, bandwidth: 1.0 Gbps] to 4 nodes with [vcpu: 1.0, memory: 5.0 Gb, disk 10.0 Gb, bandwidth: 1.0 Gbps]", + assertEquals("No allocation possible within limits: " + + "from 2 nodes with [vcpu: 1.0, memory: 5.0 Gb, disk 10.0 Gb, bandwidth: 1.0 Gbps] " + + "to 4 nodes with [vcpu: 1.0, memory: 5.0 Gb, disk 10.0 Gb, bandwidth: 1.0 Gbps]", + e.getMessage()); + } + } + + @Test + public void exclusive_resources_not_matching_host_causes_failure() { + try { + Flavor hostFlavor1 = new Flavor(new NodeResources(20, 40, 100, 4)); + Flavor hostFlavor2 = new Flavor(new NodeResources(30, 40, 100, 4)); + ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))) + .flavors(List.of(hostFlavor1, hostFlavor2)) + .build(); + ApplicationId app1 = ProvisioningTester.applicationId("app1"); + ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, + new ClusterSpec.Id("cluster1")).exclusive(true).vespaVersion("7").build(); + + var resources = new NodeResources(20, 37, 100, 1); + tester.activate(app1, cluster1, Capacity.from(new ClusterResources(2, 1, resources), + new ClusterResources(4, 1, resources))); + } + catch (IllegalArgumentException e) { + assertEquals("No allocation possible within limits: " + + "from 2 nodes with [vcpu: 20.0, memory: 37.0 Gb, disk 100.0 Gb, bandwidth: 1.0 Gbps] " + + "to 4 nodes with [vcpu: 20.0, memory: 37.0 Gb, disk 100.0 Gb, bandwidth: 1.0 Gbps]. " + + "Nearest allowed node resources: [vcpu: 20.0, memory: 40.0 Gb, disk 100.0 Gb, bandwidth: 1.0 Gbps, storage type: remote]", e.getMessage()); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java index d152cbaf5db..a28c11d009f 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java @@ -500,7 +500,7 @@ public class NodesV2ApiTest { // Attempt to DELETE allocated node tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com", new byte[0], Request.Method.DELETE), - 400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"active child node host4.yahoo.com allocated to tenant3.application3.instance3 as 'content/id3/0/0/stateful' is currently allocated and cannot be removed\"}"); + 400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"active child node host4.yahoo.com allocated to tenant3.application3.instance3 as 'content/id3/0/0/stateful' is currently allocated and cannot be removed while in active\"}"); // PUT current restart generation with string instead of long tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com", diff --git a/parent/pom.xml b/parent/pom.xml index 25851a9553e..6f1d6f23f51 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -853,7 +853,7 @@ <antlr4.version>4.5</antlr4.version> <apache.httpclient.version>4.5.12</apache.httpclient.version> <apache.httpcore.version>4.4.13</apache.httpcore.version> - <apache.httpclient5.version>5.0.3</apache.httpclient5.version> + <apache.httpclient5.version>5.1</apache.httpclient5.version> <asm.version>9.1</asm.version> <!-- Athenz dependencies. Make sure these dependencies match those in Vespa's internal repositories --> <athenz.version>1.10.14</athenz.version> @@ -890,7 +890,7 @@ <spifly.version>1.3.3</spifly.version> <surefire.version>2.22.0</surefire.version> <tensorflow.version>1.12.0</tensorflow.version> - <zookeeper.client.version>3.6.2</zookeeper.client.version> + <zookeeper.client.version>3.6.3</zookeeper.client.version> <doclint>all</doclint> <test.hide>true</test.hide> @@ -133,6 +133,7 @@ <module>vespa-athenz</module> <module>vespa-documentgen-plugin</module> <module>vespa-feed-client</module> + <module>vespa-feed-client-cli</module> <module>vespa-hadoop</module> <module>vespa-http-client</module> <module>vespa-maven-plugin</module> diff --git a/searchcore/src/apps/tests/persistenceconformance_test.cpp b/searchcore/src/apps/tests/persistenceconformance_test.cpp index 69d509c25fd..24db810b9df 100644 --- a/searchcore/src/apps/tests/persistenceconformance_test.cpp +++ b/searchcore/src/apps/tests/persistenceconformance_test.cpp @@ -133,6 +133,7 @@ public: 1, std::make_shared<RankProfilesConfig>(), std::make_shared<matching::RankingConstants>(), + std::make_shared<matching::RankingExpressions>(), std::make_shared<matching::OnnxModels>(), indexschema, attributes, diff --git a/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp b/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp index 559e8fd14cd..853395be3e1 100644 --- a/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp +++ b/searchcore/src/apps/verify_ranksetup/verify_ranksetup.cpp @@ -15,8 +15,10 @@ #include <vespa/eval/eval/value_codec.h> #include <vespa/searchcommon/common/schemaconfigurer.h> #include <vespa/searchcore/config/config-ranking-constants.h> +#include <vespa/searchcore/config/config-ranking-expressions.h> #include <vespa/searchcore/config/config-onnx-models.h> #include <vespa/searchcore/proton/matching/indexenvironment.h> +#include <vespa/searchcore/proton/matching/ranking_expressions.h> #include <vespa/searchlib/features/setup.h> #include <vespa/searchlib/fef/fef.h> #include <vespa/searchlib/fef/test/plugin/setup.h> @@ -33,11 +35,13 @@ using config::ConfigSubscriber; using config::IConfigContext; using config::InvalidConfigException; using proton::matching::IConstantValueRepo; +using proton::matching::RankingExpressions; using proton::matching::OnnxModels; using vespa::config::search::AttributesConfig; using vespa::config::search::IndexschemaConfig; using vespa::config::search::RankProfilesConfig; using vespa::config::search::core::RankingConstantsConfig; +using vespa::config::search::core::RankingExpressionsConfig; using vespa::config::search::core::OnnxModelsConfig; using vespa::config::search::core::VerifyRanksetupConfig; using vespalib::eval::BadConstantValue; @@ -57,6 +61,19 @@ std::optional<vespalib::string> get_file(const vespalib::string &ref, const Veri return std::nullopt; } +RankingExpressions make_expressions(const RankingExpressionsConfig &expressionsCfg, const VerifyRanksetupConfig &myCfg) { + RankingExpressions expressions; + for (const auto &entry: expressionsCfg.expression) { + if (auto file = get_file(entry.fileref, myCfg)) { + expressions.add(entry.name, file.value()); + } else { + LOG(warning, "could not find file for ranking expression '%s' (ref:'%s')", + entry.name.c_str(), entry.fileref.c_str()); + } + } + return expressions; +} + OnnxModels make_models(const OnnxModelsConfig &modelsCfg, const VerifyRanksetupConfig &myCfg) { OnnxModels::Vector model_list; for (const auto &entry: modelsCfg.model) { @@ -64,7 +81,7 @@ OnnxModels make_models(const OnnxModelsConfig &modelsCfg, const VerifyRanksetupC model_list.emplace_back(entry.name, file.value()); OnnxModels::configure(entry, model_list.back()); } else { - LOG(warning, "could not find file for onnx model '%s' (ref:'%s')\n", + LOG(warning, "could not find file for onnx model '%s' (ref:'%s')", entry.name.c_str(), entry.fileref.c_str()); } } @@ -77,13 +94,15 @@ public: bool verify(const search::index::Schema &schema, const search::fef::Properties &props, const IConstantValueRepo &repo, - OnnxModels models); + const RankingExpressions &expressions, + const OnnxModels &models); bool verifyConfig(const VerifyRanksetupConfig &myCfg, const RankProfilesConfig &rankCfg, const IndexschemaConfig &schemaCfg, const AttributesConfig &attributeCfg, const RankingConstantsConfig &constantsCfg, + const RankingExpressionsConfig &expressionsCfg, const OnnxModelsConfig &modelsCfg); int usage(); @@ -112,9 +131,10 @@ bool App::verify(const search::index::Schema &schema, const search::fef::Properties &props, const IConstantValueRepo &repo, - OnnxModels models) + const RankingExpressions &expressions, + const OnnxModels &models) { - proton::matching::IndexEnvironment indexEnv(0, schema, props, repo, models); + proton::matching::IndexEnvironment indexEnv(0, schema, props, repo, expressions, models); search::fef::BlueprintFactory factory; search::features::setup_search_features(factory); search::fef::test::setup_fef_test_plugin(factory); @@ -143,6 +163,7 @@ App::verifyConfig(const VerifyRanksetupConfig &myCfg, const IndexschemaConfig &schemaCfg, const AttributesConfig &attributeCfg, const RankingConstantsConfig &constantsCfg, + const RankingExpressionsConfig &expressionsCfg, const OnnxModelsConfig &modelsCfg) { bool ok = true; @@ -150,6 +171,7 @@ App::verifyConfig(const VerifyRanksetupConfig &myCfg, search::index::SchemaBuilder::build(schemaCfg, schema); search::index::SchemaBuilder::build(attributeCfg, schema); DummyConstantValueRepo repo(constantsCfg); + auto expressions = make_expressions(expressionsCfg, myCfg); auto models = make_models(modelsCfg, myCfg); for(size_t i = 0; i < rankCfg.rankprofile.size(); i++) { search::fef::Properties properties; @@ -158,7 +180,7 @@ App::verifyConfig(const VerifyRanksetupConfig &myCfg, properties.add(profile.fef.property[j].name, profile.fef.property[j].value); } - if (verify(schema, properties, repo, models)) { + if (verify(schema, properties, repo, expressions, models)) { LOG(info, "rank profile '%s': pass", profile.name.c_str()); } else { LOG(error, "rank profile '%s': FAIL", profile.name.c_str()); @@ -196,6 +218,7 @@ App::Main() ConfigHandle<AttributesConfig>::UP attributesHandle = subscriber.subscribe<AttributesConfig>(cfgId); ConfigHandle<IndexschemaConfig>::UP schemaHandle = subscriber.subscribe<IndexschemaConfig>(cfgId); ConfigHandle<RankingConstantsConfig>::UP constantsHandle = subscriber.subscribe<RankingConstantsConfig>(cfgId); + ConfigHandle<RankingExpressionsConfig>::UP expressionsHandle = subscriber.subscribe<RankingExpressionsConfig>(cfgId); ConfigHandle<OnnxModelsConfig>::UP modelsHandle = subscriber.subscribe<OnnxModelsConfig>(cfgId); subscriber.nextConfig(); @@ -204,6 +227,7 @@ App::Main() *schemaHandle->getConfig(), *attributesHandle->getConfig(), *constantsHandle->getConfig(), + *expressionsHandle->getConfig(), *modelsHandle->getConfig()); } catch (ConfigRuntimeException & e) { LOG(error, "Unable to subscribe to config: %s", e.getMessage().c_str()); diff --git a/searchcore/src/apps/vespa-feed-bm/vespa_feed_bm.cpp b/searchcore/src/apps/vespa-feed-bm/vespa_feed_bm.cpp index 6e346bcfa60..e4dd4715ed1 100644 --- a/searchcore/src/apps/vespa-feed-bm/vespa_feed_bm.cpp +++ b/searchcore/src/apps/vespa-feed-bm/vespa_feed_bm.cpp @@ -187,6 +187,7 @@ std::shared_ptr<DocumentDBConfig> make_document_db_config(std::shared_ptr<Docume 1, std::make_shared<RankProfilesConfig>(), std::make_shared<matching::RankingConstants>(), + std::make_shared<matching::RankingExpressions>(), std::make_shared<matching::OnnxModels>(), indexschema, attributes, diff --git a/searchcore/src/apps/vespa-proton-cmd/vespa-proton-cmd.cpp b/searchcore/src/apps/vespa-proton-cmd/vespa-proton-cmd.cpp index 8b6203c78a2..50a2aecce76 100644 --- a/searchcore/src/apps/vespa-proton-cmd/vespa-proton-cmd.cpp +++ b/searchcore/src/apps/vespa-proton-cmd/vespa-proton-cmd.cpp @@ -3,6 +3,7 @@ #include <vespa/slobrok/sbmirror.h> #include <vespa/config/common/configsystem.h> #include <vespa/config/common/exceptions.h> +#include <vespa/vespalib/util/exceptions.h> #include <vespa/fnet/frt/supervisor.h> #include <vespa/fnet/frt/target.h> #include <vespa/vespalib/util/host_name.h> @@ -158,6 +159,9 @@ public: } catch (config::InvalidConfigException& e) { fprintf(stderr, "ERROR: failed to get service location broker configuration\n"); std::_Exit(1); + } catch (vespalib::IllegalStateException& e) { + fprintf(stderr, "ERROR: empty or invalid service location broker configuration: %s\n", e.what()); + std::_Exit(2); } return ""; } diff --git a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp index 7ae828241c8..3f65f61cbaa 100644 --- a/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp +++ b/searchcore/src/tests/proton/documentdb/configurer/configurer_test.cpp @@ -643,6 +643,7 @@ TEST("require that maintenance controller should change if some config has chang TEST_DO(assertMaintenanceControllerShouldChange(CCR().setRankProfilesChanged(true))); TEST_DO(assertMaintenanceControllerShouldChange(CCR().setRankingConstantsChanged(true))); + TEST_DO(assertMaintenanceControllerShouldChange(CCR().setRankingExpressionsChanged(true))); TEST_DO(assertMaintenanceControllerShouldChange(CCR().setOnnxModelsChanged(true))); TEST_DO(assertMaintenanceControllerShouldChange(CCR().setIndexschemaChanged(true))); TEST_DO(assertMaintenanceControllerShouldChange(CCR().setAttributesChanged(true))); @@ -690,6 +691,7 @@ TEST("require that subdbs should change if relevant config changed") TEST_DO(assertSubDbsShouldChange(CCR().setVisibilityDelayChanged(true))); TEST_DO(assertSubDbsShouldChange(CCR().setRankProfilesChanged(true))); TEST_DO(assertSubDbsShouldChange(CCR().setRankingConstantsChanged(true))); + TEST_DO(assertSubDbsShouldChange(CCR().setRankingExpressionsChanged(true))); TEST_DO(assertSubDbsShouldChange(CCR().setOnnxModelsChanged(true))); TEST_DO(assertSubDbsShouldChange(CCR().setSchemaChanged(true))); TEST_DO(assertSubDbsShouldChange(CCR().set_alloc_config_changed(true))); diff --git a/searchcore/src/tests/proton/documentdb/documentdbconfig/documentdbconfig_test.cpp b/searchcore/src/tests/proton/documentdb/documentdbconfig/documentdbconfig_test.cpp index aed01ca0192..544ff853384 100644 --- a/searchcore/src/tests/proton/documentdb/documentdbconfig/documentdbconfig_test.cpp +++ b/searchcore/src/tests/proton/documentdb/documentdbconfig/documentdbconfig_test.cpp @@ -17,6 +17,7 @@ using namespace search::index; using namespace search; using namespace vespa::config::search; using proton::matching::RankingConstants; +using proton::matching::RankingExpressions; using proton::matching::OnnxModels; using std::make_shared; using std::shared_ptr; @@ -69,6 +70,11 @@ public: _builder.rankingConstants(make_shared<RankingConstants>(constants)); return *this; } + MyConfigBuilder &addRankingExpression() { + auto expr_list = RankingExpressions().add("my_expr", "my_file"); + _builder.rankingExpressions(make_shared<RankingExpressions>(expr_list)); + return *this; + } MyConfigBuilder &addOnnxModel() { OnnxModels::Vector models = {{"my_model_name", "my_model_file"}}; _builder.onnxModels(make_shared<OnnxModels>(models)); @@ -138,6 +144,7 @@ struct Fixture { fullCfg = MyConfigBuilder(4, schema, repo).addAttribute(). addRankProfile(). addRankingConstant(). + addRankingExpression(). addOnnxModel(). addImportedField(). addSummary(true). @@ -173,6 +180,7 @@ struct DelayAttributeAspectFixture { attrCfg = MyConfigBuilder(4, schema, makeDocTypeRepo(true)).addAttribute(). addRankProfile(). addRankingConstant(). + addRankingExpression(). addOnnxModel(). addImportedField(). addSummary(true). @@ -180,6 +188,7 @@ struct DelayAttributeAspectFixture { build(); noAttrCfg = MyConfigBuilder(4, schema, makeDocTypeRepo(hasDocField)).addRankProfile(). addRankingConstant(). + addRankingExpression(). addOnnxModel(). addImportedField(). addSummary(hasDocField). diff --git a/searchcore/src/tests/proton/documentdb/fileconfigmanager/fileconfigmanager_test.cpp b/searchcore/src/tests/proton/documentdb/fileconfigmanager/fileconfigmanager_test.cpp index e6bcbf18495..4ac66e464e2 100644 --- a/searchcore/src/tests/proton/documentdb/fileconfigmanager/fileconfigmanager_test.cpp +++ b/searchcore/src/tests/proton/documentdb/fileconfigmanager/fileconfigmanager_test.cpp @@ -28,6 +28,7 @@ using namespace vespa::config::search; using namespace std::chrono_literals; using vespa::config::content::core::BucketspacesConfig; using proton::matching::RankingConstants; +using proton::matching::RankingExpressions; using proton::matching::OnnxModels; typedef DocumentDBConfigHelper DBCM; @@ -76,8 +77,10 @@ assertEqualSnapshot(const DocumentDBConfig &exp, const DocumentDBConfig &act) { EXPECT_TRUE(exp.getRankProfilesConfig() == act.getRankProfilesConfig()); EXPECT_TRUE(exp.getRankingConstants() == act.getRankingConstants()); + EXPECT_TRUE(exp.getRankingExpressions() == act.getRankingExpressions()); EXPECT_TRUE(exp.getOnnxModels() == act.getOnnxModels()); EXPECT_EQUAL(0u, exp.getRankingConstants().size()); + EXPECT_EQUAL(0u, exp.getRankingExpressions().size()); EXPECT_EQUAL(0u, exp.getOnnxModels().size()); EXPECT_TRUE(exp.getIndexschemaConfig() == act.getIndexschemaConfig()); EXPECT_TRUE(exp.getAttributesConfig() == act.getAttributesConfig()); @@ -108,6 +111,9 @@ addConfigsThatAreNotSavedToDisk(const DocumentDBConfig &cfg) RankingConstants::Vector constants = {{"my_name", "my_type", "my_path"}}; builder.rankingConstants(std::make_shared<RankingConstants>(constants)); + auto expr_list = RankingExpressions().add("my_expr", "my_file"); + builder.rankingExpressions(std::make_shared<RankingExpressions>(expr_list)); + OnnxModels::Vector models = {{"my_model_name", "my_model_file"}}; builder.onnxModels(std::make_shared<OnnxModels>(models)); diff --git a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp index 67a7ceeae34..59bb33f51ce 100644 --- a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp +++ b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp @@ -908,7 +908,6 @@ TEST_F("require that document pruner is active", MaintenanceControllerFixture) f._builder.createDocs(3, 6, 8); // 2 docs f._builder.createDocs(4, 8, 11); // 3 docs test::UserDocuments removeDocs(f._builder.getDocs()); - BucketId bucketId3(removeDocs.getBucket(3)); f.removeDocs(removeDocs, remTime); f.notifyClusterStateChanged(); EXPECT_TRUE(f._executor.isIdle()); diff --git a/searchcore/src/tests/proton/matching/index_environment/.gitattributes b/searchcore/src/tests/proton/matching/index_environment/.gitattributes new file mode 100644 index 00000000000..c7aff0d0972 --- /dev/null +++ b/searchcore/src/tests/proton/matching/index_environment/.gitattributes @@ -0,0 +1 @@ +/my_expr.lz4 binary 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 421ebffafa4..20b9e1206c7 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 @@ -3,6 +3,7 @@ #include <vespa/eval/eval/value_cache/constant_value.h> #include <vespa/searchcore/proton/matching/indexenvironment.h> +#include <vespa/searchcore/proton/matching/ranking_expressions.h> using namespace proton::matching; using search::fef::FieldInfo; @@ -15,6 +16,21 @@ using search::index::schema::DataType; using vespalib::eval::ConstantValue; using SIAF = Schema::ImportedAttributeField; +const vespalib::string my_expr_ref( + "this is my reference ranking expression.\n" + "this is my reference ranking expression.\n" + "it will not compile into a function.\n" + "it will not compile into a function.\n" + "it is just some text, that can also be compressed...\n" + "it is just some text, that can also be compressed...\n"); + +RankingExpressions make_expressions() { + RankingExpressions expr_list; + expr_list.add("expr1", TEST_PATH("my_expr")); + expr_list.add("expr2", TEST_PATH("my_expr.lz4")); + return expr_list; +} + OnnxModels make_models() { OnnxModels::Vector list; list.emplace_back(OnnxModel("model1", "path1").input_feature("input1","feature1").output_name("output1", "out1")); @@ -50,7 +66,7 @@ struct Fixture { Fixture(Schema::UP schema_) : repo(), schema(std::move(schema_)), - env(7, *schema, Properties(), repo, make_models()) + env(7, *schema, Properties(), repo, make_expressions(), make_models()) { } const FieldInfo *assertField(size_t idx, @@ -123,4 +139,13 @@ TEST_F("require that onnx model config can be obtained", Fixture(buildEmptySchem EXPECT_TRUE(f1.env.getOnnxModel("model3") == nullptr); } +TEST_F("require that external ranking expressions can be obtained", Fixture(buildEmptySchema())) { + auto expr1 = f1.env.getRankingExpression("expr1"); + auto expr2 = f1.env.getRankingExpression("expr2"); + auto expr3 = f1.env.getRankingExpression("expr3"); + EXPECT_EQUAL(expr1, my_expr_ref); + EXPECT_EQUAL(expr2, my_expr_ref); + EXPECT_TRUE(expr3.empty()); +} + TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchcore/src/tests/proton/matching/index_environment/my_expr b/searchcore/src/tests/proton/matching/index_environment/my_expr new file mode 100644 index 00000000000..2fdd1e4c9ff --- /dev/null +++ b/searchcore/src/tests/proton/matching/index_environment/my_expr @@ -0,0 +1,6 @@ +this is my reference ranking expression. +this is my reference ranking expression. +it will not compile into a function. +it will not compile into a function. +it is just some text, that can also be compressed... +it is just some text, that can also be compressed... diff --git a/searchcore/src/tests/proton/matching/index_environment/my_expr.lz4 b/searchcore/src/tests/proton/matching/index_environment/my_expr.lz4 Binary files differnew file mode 100644 index 00000000000..c88594ce81d --- /dev/null +++ b/searchcore/src/tests/proton/matching/index_environment/my_expr.lz4 diff --git a/searchcore/src/tests/proton/matching/matching_test.cpp b/searchcore/src/tests/proton/matching/matching_test.cpp index 8057dbf2da6..2bee53a5215 100644 --- a/searchcore/src/tests/proton/matching/matching_test.cpp +++ b/searchcore/src/tests/proton/matching/matching_test.cpp @@ -280,7 +280,7 @@ struct MyWorld { } Matcher::SP createMatcher() { - return std::make_shared<Matcher>(schema, config, clock, queryLimiter, constantValueRepo, OnnxModels(), 0); + return std::make_shared<Matcher>(schema, config, clock, queryLimiter, constantValueRepo, RankingExpressions(), OnnxModels(), 0); } struct MySearchHandler : ISearchHandler { diff --git a/searchcore/src/tests/proton/proton_config_fetcher/proton_config_fetcher_test.cpp b/searchcore/src/tests/proton/proton_config_fetcher/proton_config_fetcher_test.cpp index e42f8323473..20a10aee678 100644 --- a/searchcore/src/tests/proton/proton_config_fetcher/proton_config_fetcher_test.cpp +++ b/searchcore/src/tests/proton/proton_config_fetcher/proton_config_fetcher_test.cpp @@ -10,6 +10,7 @@ #include <vespa/searchcore/proton/common/hw_info.h> #include <vespa/searchcore/proton/common/subdbtype.h> #include <vespa/searchcore/config/config-ranking-constants.h> +#include <vespa/searchcore/config/config-ranking-expressions.h> #include <vespa/searchcore/config/config-onnx-models.h> #include <vespa/searchsummary/config/config-juniperrc.h> #include <vespa/document/repo/documenttyperepo.h> @@ -52,6 +53,7 @@ struct DoctypeFixture { AttributesConfigBuilder attributesBuilder; RankProfilesConfigBuilder rankProfilesBuilder; RankingConstantsConfigBuilder rankingConstantsBuilder; + RankingExpressionsConfigBuilder rankingExpressionsBuilder; OnnxModelsConfigBuilder onnxModelsBuilder; IndexschemaConfigBuilder indexschemaBuilder; SummaryConfigBuilder summaryBuilder; @@ -108,6 +110,7 @@ struct ConfigTestFixture { set.addBuilder(db.configid, &fixture->attributesBuilder); set.addBuilder(db.configid, &fixture->rankProfilesBuilder); set.addBuilder(db.configid, &fixture->rankingConstantsBuilder); + set.addBuilder(db.configid, &fixture->rankingExpressionsBuilder); set.addBuilder(db.configid, &fixture->onnxModelsBuilder); set.addBuilder(db.configid, &fixture->indexschemaBuilder); set.addBuilder(db.configid, &fixture->summaryBuilder); @@ -262,7 +265,7 @@ TEST_FF("require that documentdb config manager subscribes for config", DocumentDBConfigManager(f1.configId + "/typea", "typea")) { f1.addDocType("typea"); const ConfigKeySet keySet(f2.createConfigKeySet()); - ASSERT_EQUAL(9u, keySet.size()); + ASSERT_EQUAL(10u, keySet.size()); ASSERT_TRUE(f1.configEqual("typea", getDocumentDBConfig(f1, f2))); } diff --git a/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp b/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp index 16e2fc4b1a8..faf09be3bbd 100644 --- a/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp +++ b/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp @@ -48,14 +48,13 @@ using std::map; using search::index::Schema; using search::index::SchemaBuilder; using proton::matching::RankingConstants; +using proton::matching::RankingExpressions; using proton::matching::OnnxModels; struct DBConfigFixture { using UP = std::unique_ptr<DBConfigFixture>; AttributesConfigBuilder _attributesBuilder; RankProfilesConfigBuilder _rankProfilesBuilder; - RankingConstantsConfigBuilder _rankingConstantsBuilder; - OnnxModelsConfigBuilder _onnxModelsBuilder; IndexschemaConfigBuilder _indexschemaBuilder; SummaryConfigBuilder _summaryBuilder; SummarymapConfigBuilder _summarymapBuilder; @@ -76,6 +75,11 @@ struct DBConfigFixture { return std::make_shared<RankingConstants>(); } + RankingExpressions::SP buildRankingExpressions() + { + return std::make_shared<RankingExpressions>(); + } + OnnxModels::SP buildOnnxModels() { return std::make_shared<OnnxModels>(); @@ -91,6 +95,7 @@ struct DBConfigFixture { (generation, std::make_shared<RankProfilesConfig>(_rankProfilesBuilder), buildRankingConstants(), + buildRankingExpressions(), buildOnnxModels(), std::make_shared<IndexschemaConfig>(_indexschemaBuilder), std::make_shared<AttributesConfig>(_attributesBuilder), diff --git a/searchcore/src/tests/proton/verify_ranksetup/bad_ranking_expression b/searchcore/src/tests/proton/verify_ranksetup/bad_ranking_expression new file mode 100644 index 00000000000..475b311dc84 --- /dev/null +++ b/searchcore/src/tests/proton/verify_ranksetup/bad_ranking_expression @@ -0,0 +1 @@ +join(constant(my_tensor),attribute(date),f(t,d)(join(t,d,f(x,y)(x+y)))) diff --git a/searchcore/src/tests/proton/verify_ranksetup/good_ranking_expression b/searchcore/src/tests/proton/verify_ranksetup/good_ranking_expression new file mode 100644 index 00000000000..f6712be7a82 --- /dev/null +++ b/searchcore/src/tests/proton/verify_ranksetup/good_ranking_expression @@ -0,0 +1 @@ +join(constant(my_tensor),attribute(date),f(t,d)(t+d)) diff --git a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp index c46990732b7..31557f13a54 100644 --- a/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp +++ b/searchcore/src/tests/proton/verify_ranksetup/verify_ranksetup_test.cpp @@ -71,6 +71,7 @@ struct Setup { std::map<std::string,std::string> properties; std::map<std::string,std::string> constants; std::vector<bool> extra_profiles; + std::map<std::string,std::string> ranking_expressions; std::map<std::string,OnnxModel> onnx_models; Setup(); ~Setup(); @@ -96,6 +97,9 @@ struct Setup { void rank_expr(const std::string &name, const std::string &expr) { property(fmt("rankingExpression(%s).rankingScript", name.c_str()), expr); } + void ext_rank_expr(const std::string &name, const std::string &file) { + ranking_expressions.insert_or_assign(name, TEST_PATH(file)); + } void first_phase(const std::string &feature) { property(rank::FirstPhase::NAME, feature); } @@ -157,6 +161,14 @@ struct Setup { ++idx; } } + void write_ranking_expressions(const Writer &out) { + size_t idx = 0; + for (const auto &entry: ranking_expressions) { + out.fmt("expression[%zu].name \"%s\"\n", idx, entry.first.c_str()); + out.fmt("expression[%zu].fileref \"expr_ref_%zu\"\n", idx, idx); + ++idx; + } + } void write_onnx_models(const Writer &out) { size_t idx = 0; for (const auto &entry: onnx_models) { @@ -179,6 +191,12 @@ struct Setup { } void write_self_cfg(const Writer &out) { size_t idx = 0; + for (const auto &entry: ranking_expressions) { + out.fmt("file[%zu].ref \"expr_ref_%zu\"\n", idx, idx); + out.fmt("file[%zu].path \"%s\"\n", idx, entry.second.c_str()); + ++idx; + } + idx = 0; for (const auto &entry: onnx_models) { out.fmt("file[%zu].ref \"onnx_ref_%zu\"\n", idx, idx); out.fmt("file[%zu].path \"%s\"\n", idx, entry.second.file_path().c_str()); @@ -190,6 +208,7 @@ struct Setup { write_indexschema(Writer(gen_dir + "/indexschema.cfg")); write_rank_profiles(Writer(gen_dir + "/rank-profiles.cfg")); write_ranking_constants(Writer(gen_dir + "/ranking-constants.cfg")); + write_ranking_expressions(Writer(gen_dir + "/ranking-expressions.cfg")); write_onnx_models(Writer(gen_dir + "/onnx-models.cfg")); write_self_cfg(Writer(gen_dir + "/verify-ranksetup.cfg")); } @@ -358,6 +377,23 @@ TEST_F("require that imported attribute field can be used by rank feature", Simp //----------------------------------------------------------------------------- +TEST_F("require that external ranking expression can be verified", SimpleSetup()) { + f.ext_rank_expr("my_expr", "good_ranking_expression"); + f.verify_valid({"rankingExpression(my_expr)"}); +} + +TEST_F("require that external ranking expression can fail verification", SimpleSetup()) { + f.ext_rank_expr("my_expr", "bad_ranking_expression"); + f.verify_invalid({"rankingExpression(my_expr)"}); +} + +TEST_F("require that missing expression file fails verification", SimpleSetup()) { + f.ext_rank_expr("my_expr", "missing_ranking_expression_file"); + f.verify_invalid({"rankingExpression(my_expr)"}); +} + +//----------------------------------------------------------------------------- + TEST_F("require that onnx model can be verified", OnnxSetup()) { f.rank_expr("query_tensor", "tensor<float>(a[1],b[4]):[[1,2,3,4]]"); f.rank_expr("attribute_tensor", "tensor<float>(a[4],b[1]):[[5],[6],[7],[8]]"); diff --git a/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt index a4688b5fdca..d83d8ed91d9 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/proton/matching/CMakeLists.txt @@ -27,6 +27,7 @@ vespa_add_library(searchcore_matching STATIC querylimiter.cpp querynodes.cpp ranking_constants.cpp + ranking_expressions.cpp requestcontext.cpp resolveviewvisitor.cpp result_processor.cpp diff --git a/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp b/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp index 013f359c4f9..5f5cc4dcf76 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.cpp @@ -65,15 +65,17 @@ IndexEnvironment::IndexEnvironment(uint32_t distributionKey, const search::index::Schema &schema, const search::fef::Properties &props, const IConstantValueRepo &constantValueRepo, + RankingExpressions rankingExpressions, OnnxModels onnxModels) - : _tableManager(), - _properties(props), - _fieldNames(), - _fields(), - _motivation(UNKNOWN), - _constantValueRepo(constantValueRepo), - _onnxModels(std::move(onnxModels)), - _distributionKey(distributionKey) + : _tableManager(), + _properties(props), + _fieldNames(), + _fields(), + _motivation(UNKNOWN), + _constantValueRepo(constantValueRepo), + _rankingExpressions(std::move(rankingExpressions)), + _onnxModels(std::move(onnxModels)), + _distributionKey(distributionKey) { _tableManager.addFactory(std::make_shared<search::fef::FunctionTableFactory>(256)); extractFields(schema); @@ -131,6 +133,12 @@ IndexEnvironment::hintFieldAccess(uint32_t ) const { } void IndexEnvironment::hintAttributeAccess(const string &) const { } +vespalib::string +IndexEnvironment::getRankingExpression(const vespalib::string &name) const +{ + return _rankingExpressions.loadExpression(name); +} + const search::fef::OnnxModel * IndexEnvironment::getOnnxModel(const vespalib::string &name) const { diff --git a/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.h b/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.h index ad51eb17b4d..d270825d3f7 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.h +++ b/searchcore/src/vespa/searchcore/proton/matching/indexenvironment.h @@ -3,6 +3,7 @@ #pragma once #include "onnx_models.h" +#include "ranking_expressions.h" #include "i_constant_value_repo.h" #include <vespa/searchlib/fef/fieldinfo.h> #include <vespa/searchlib/fef/iindexenvironment.h> @@ -26,6 +27,7 @@ private: std::vector<search::fef::FieldInfo> _fields; mutable FeatureMotivation _motivation; const IConstantValueRepo &_constantValueRepo; + RankingExpressions _rankingExpressions; OnnxModels _onnxModels; uint32_t _distributionKey; @@ -46,12 +48,14 @@ public: * @param schema the index schema * @param props config * @param constantValueRepo repo used to access constant values for ranking + * @param rankingExpressions processed config about ranking expressions * @param onnxModels processed config about onnx models **/ IndexEnvironment(uint32_t distributionKey, const search::index::Schema &schema, const search::fef::Properties &props, const IConstantValueRepo &constantValueRepo, + RankingExpressions rankingExpressions, OnnxModels onnxModels); const search::fef::Properties &getProperties() const override; @@ -68,6 +72,7 @@ public: vespalib::eval::ConstantValue::UP getConstantValue(const vespalib::string &name) const override { return _constantValueRepo.getConstant(name); } + vespalib::string getRankingExpression(const vespalib::string &name) const override; const search::fef::OnnxModel *getOnnxModel(const vespalib::string &name) const override; ~IndexEnvironment() override; diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp index e459a45040b..806753a6660 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp @@ -99,17 +99,18 @@ handleGroupingSession(SessionManager &sessionMgr, GroupingContext & groupingCont } // namespace proton::matching::<unnamed> Matcher::Matcher(const search::index::Schema &schema, const Properties &props, const vespalib::Clock &clock, - QueryLimiter &queryLimiter, const IConstantValueRepo &constantValueRepo, OnnxModels onnxModels, uint32_t distributionKey) - : _indexEnv(distributionKey, schema, props, constantValueRepo, std::move(onnxModels)), - _blueprintFactory(), - _rankSetup(), - _viewResolver(ViewResolver::createFromSchema(schema)), - _statsLock(), - _stats(), - _startTime(my_clock::now()), - _clock(clock), - _queryLimiter(queryLimiter), - _distributionKey(distributionKey) + QueryLimiter &queryLimiter, const IConstantValueRepo &constantValueRepo, + RankingExpressions rankingExpressions, OnnxModels onnxModels, uint32_t distributionKey) + : _indexEnv(distributionKey, schema, props, constantValueRepo, std::move(rankingExpressions), std::move(onnxModels)), + _blueprintFactory(), + _rankSetup(), + _viewResolver(ViewResolver::createFromSchema(schema)), + _statsLock(), + _stats(), + _startTime(my_clock::now()), + _clock(clock), + _queryLimiter(queryLimiter), + _distributionKey(distributionKey) { search::features::setup_search_features(_blueprintFactory); search::fef::test::setup_fef_test_plugin(_blueprintFactory); diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.h b/searchcore/src/vespa/searchcore/proton/matching/matcher.h index dcd1bbb2b46..d40ae9b18f1 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/matcher.h +++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.h @@ -88,7 +88,8 @@ public: **/ Matcher(const search::index::Schema &schema, const Properties &props, const vespalib::Clock &clock, QueryLimiter &queryLimiter, - const IConstantValueRepo &constantValueRepo, OnnxModels onnxModels, + const IConstantValueRepo &constantValueRepo, + RankingExpressions rankingExpressions, OnnxModels onnxModels, uint32_t distributionKey); const search::fef::IIndexEnvironment &get_index_env() const { return _indexEnv; } diff --git a/searchcore/src/vespa/searchcore/proton/matching/ranking_expressions.cpp b/searchcore/src/vespa/searchcore/proton/matching/ranking_expressions.cpp new file mode 100644 index 00000000000..9b74f76aa6e --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/matching/ranking_expressions.cpp @@ -0,0 +1,67 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "ranking_expressions.h" +#include <vespa/vespalib/io/mapped_file_input.h> +#include <vespa/vespalib/data/lz4_input_decoder.h> +#include <vespa/vespalib/util/size_literals.h> + +#include <vespa/log/log.h> +LOG_SETUP(".proton.matching.ranking_expressions"); + +namespace proton::matching { + +namespace { + +vespalib::string extract_data(vespalib::Input &input) { + vespalib::string result; + for (auto chunk = input.obtain(); chunk.size > 0; chunk = input.obtain()) { + result.append(vespalib::stringref(chunk.data, chunk.size)); + input.evict(chunk.size); + } + return result; +} + +} // unnamed + +RankingExpressions::RankingExpressions() = default; +RankingExpressions::RankingExpressions(RankingExpressions &&rhs) = default; +RankingExpressions::RankingExpressions(const RankingExpressions &rhs) = default; +RankingExpressions::~RankingExpressions() = default; + +RankingExpressions & +RankingExpressions::add(const vespalib::string &name, const vespalib::string &path) +{ + _expressions.insert_or_assign(name, path); + return *this; +} + +vespalib::string +RankingExpressions::loadExpression(const vespalib::string &name) const +{ + auto pos = _expressions.find(name); + if (pos == _expressions.end()) { + // not warning about missing expression here since what we + // think is a name might be an expression itself. + return {}; + } + auto path = pos->second; + vespalib::MappedFileInput file(path); + if (!file.valid()) { + LOG(warning, "could not read file: %s", path.c_str()); + return {}; + } + if (ends_with(path, ".lz4")) { + size_t buffer_size = 64_Ki; + vespalib::Lz4InputDecoder lz4_decoder(file, buffer_size); + auto result = extract_data(lz4_decoder); + if (lz4_decoder.failed()) { + LOG(warning, "file contains lz4 errors (%s): %s", + lz4_decoder.reason().c_str(), path.c_str()); + return {}; + } + return result; + } + return extract_data(file); +} + +} diff --git a/searchcore/src/vespa/searchcore/proton/matching/ranking_expressions.h b/searchcore/src/vespa/searchcore/proton/matching/ranking_expressions.h new file mode 100644 index 00000000000..e1472b03ee0 --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/matching/ranking_expressions.h @@ -0,0 +1,35 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/stllike/string.h> +#include <memory> +#include <map> + +namespace proton::matching { + +/** + * Class representing a collection of named ranking expressions + * obtained through file-distribution. + */ +class RankingExpressions +{ +private: + // expression name -> full_path of expression file + std::map<vespalib::string,vespalib::string> _expressions; + +public: + RankingExpressions(); + RankingExpressions(RankingExpressions &&rhs); + RankingExpressions(const RankingExpressions &rhs); + ~RankingExpressions(); + using SP = std::shared_ptr<RankingExpressions>; + bool operator==(const RankingExpressions &rhs) const { + return _expressions == rhs._expressions; + } + size_t size() const { return _expressions.size(); } + RankingExpressions &add(const vespalib::string &name, const vespalib::string &path); + vespalib::string loadExpression(const vespalib::string &name) const; +}; + +} diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp index 7c487043b5b..e39188042d7 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.cpp @@ -35,6 +35,7 @@ namespace proton { DocumentDBConfig::ComparisonResult::ComparisonResult() : rankProfilesChanged(false), rankingConstantsChanged(false), + rankingExpressionsChanged(false), onnxModelsChanged(false), indexschemaChanged(false), attributesChanged(false), @@ -58,6 +59,7 @@ DocumentDBConfig::DocumentDBConfig( int64_t generation, const RankProfilesConfigSP &rankProfiles, const RankingConstants::SP &rankingConstants, + const RankingExpressions::SP &rankingExpressions, const OnnxModels::SP &onnxModels, const IndexschemaConfigSP &indexschema, const AttributesConfigSP &attributes, @@ -80,6 +82,7 @@ DocumentDBConfig::DocumentDBConfig( _generation(generation), _rankProfiles(rankProfiles), _rankingConstants(rankingConstants), + _rankingExpressions(rankingExpressions), _onnxModels(onnxModels), _indexschema(indexschema), _attributes(attributes), @@ -107,6 +110,7 @@ DocumentDBConfig(const DocumentDBConfig &cfg) _generation(cfg._generation), _rankProfiles(cfg._rankProfiles), _rankingConstants(cfg._rankingConstants), + _rankingExpressions(cfg._rankingExpressions), _onnxModels(cfg._onnxModels), _indexschema(cfg._indexschema), _attributes(cfg._attributes), @@ -133,6 +137,7 @@ DocumentDBConfig::operator==(const DocumentDBConfig & rhs) const { return equals<RankProfilesConfig>(_rankProfiles.get(), rhs._rankProfiles.get()) && equals<RankingConstants>(_rankingConstants.get(), rhs._rankingConstants.get()) && + equals<RankingExpressions>(_rankingExpressions.get(), rhs._rankingExpressions.get()) && equals<OnnxModels>(_onnxModels.get(), rhs._onnxModels.get()) && equals<IndexschemaConfig>(_indexschema.get(), rhs._indexschema.get()) && equals<AttributesConfig>(_attributes.get(), rhs._attributes.get()) && @@ -157,6 +162,7 @@ DocumentDBConfig::compare(const DocumentDBConfig &rhs) const ComparisonResult retval; retval.rankProfilesChanged = !equals<RankProfilesConfig>(_rankProfiles.get(), rhs._rankProfiles.get()); retval.rankingConstantsChanged = !equals<RankingConstants>(_rankingConstants.get(), rhs._rankingConstants.get()); + retval.rankingExpressionsChanged = !equals<RankingExpressions>(_rankingExpressions.get(), rhs._rankingExpressions.get()); retval.onnxModelsChanged = !equals<OnnxModels>(_onnxModels.get(), rhs._onnxModels.get()); retval.indexschemaChanged = !equals<IndexschemaConfig>(_indexschema.get(), rhs._indexschema.get()); retval.attributesChanged = !equals<AttributesConfig>(_attributes.get(), rhs._attributes.get()); @@ -183,6 +189,7 @@ DocumentDBConfig::valid() const { return _rankProfiles && _rankingConstants && + _rankingExpressions && _onnxModels && _indexschema && _attributes && @@ -226,6 +233,7 @@ DocumentDBConfig::makeReplayConfig(const SP & orig) o._generation, emptyConfig(o._rankProfiles), std::make_shared<RankingConstants>(), + std::make_shared<RankingExpressions>(), std::make_shared<OnnxModels>(), o._indexschema, o._attributes, @@ -269,6 +277,7 @@ DocumentDBConfig::newFromAttributesConfig(const AttributesConfigSP &attributes) _generation, _rankProfiles, _rankingConstants, + _rankingExpressions, _onnxModels, _indexschema, attributes, @@ -307,6 +316,7 @@ DocumentDBConfig::makeDelayedAttributeAspectConfig(const SP &newCfg, const Docum (n._generation, n._rankProfiles, n._rankingConstants, + n._rankingExpressions, n._onnxModels, n._indexschema, attributeAspectDelayer.getAttributesConfig(), diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h index 8e24ed8e96a..2ca7fb1dfa8 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfig.h @@ -6,6 +6,7 @@ #include <vespa/searchlib/common/tunefileinfo.h> #include <vespa/searchcommon/common/schema.h> #include <vespa/searchcore/proton/matching/ranking_constants.h> +#include <vespa/searchcore/proton/matching/ranking_expressions.h> #include <vespa/searchcore/proton/matching/onnx_models.h> #include <vespa/config/retriever/configkeyset.h> #include <vespa/config/retriever/configsnapshot.h> @@ -40,6 +41,7 @@ public: public: bool rankProfilesChanged; bool rankingConstantsChanged; + bool rankingExpressionsChanged; bool onnxModelsChanged; bool indexschemaChanged; bool attributesChanged; @@ -61,6 +63,7 @@ public: ComparisonResult(); ComparisonResult &setRankProfilesChanged(bool val) { rankProfilesChanged = val; return *this; } ComparisonResult &setRankingConstantsChanged(bool val) { rankingConstantsChanged = val; return *this; } + ComparisonResult &setRankingExpressionsChanged(bool val) { rankingExpressionsChanged = val; return *this; } ComparisonResult &setOnnxModelsChanged(bool val) { onnxModelsChanged = val; return *this; } ComparisonResult &setIndexschemaChanged(bool val) { indexschemaChanged = val; return *this; } ComparisonResult &setAttributesChanged(bool val) { attributesChanged = val; return *this; } @@ -101,6 +104,7 @@ public: using RankProfilesConfig = const vespa::config::search::internal::InternalRankProfilesType; using RankProfilesConfigSP = std::shared_ptr<RankProfilesConfig>; using RankingConstants = matching::RankingConstants; + using RankingExpressions = matching::RankingExpressions; using OnnxModels = matching::OnnxModels; using SummaryConfig = const vespa::config::search::internal::InternalSummaryType; using SummaryConfigSP = std::shared_ptr<SummaryConfig>; @@ -120,6 +124,7 @@ private: int64_t _generation; RankProfilesConfigSP _rankProfiles; RankingConstants::SP _rankingConstants; + RankingExpressions::SP _rankingExpressions; OnnxModels::SP _onnxModels; IndexschemaConfigSP _indexschema; AttributesConfigSP _attributes; @@ -159,6 +164,7 @@ public: DocumentDBConfig(int64_t generation, const RankProfilesConfigSP &rankProfiles, const RankingConstants::SP &rankingConstants, + const RankingExpressions::SP &rankingExpressions, const OnnxModels::SP &onnxModels, const IndexschemaConfigSP &indexschema, const AttributesConfigSP &attributes, @@ -189,6 +195,7 @@ public: const RankProfilesConfig &getRankProfilesConfig() const { return *_rankProfiles; } const RankingConstants &getRankingConstants() const { return *_rankingConstants; } + const RankingExpressions &getRankingExpressions() const { return *_rankingExpressions; } const OnnxModels &getOnnxModels() const { return *_onnxModels; } const IndexschemaConfig &getIndexschemaConfig() const { return *_indexschema; } const AttributesConfig &getAttributesConfig() const { return *_attributes; } @@ -198,6 +205,7 @@ public: const DocumenttypesConfig &getDocumenttypesConfig() const { return *_documenttypes; } const RankProfilesConfigSP &getRankProfilesConfigSP() const { return _rankProfiles; } const RankingConstants::SP &getRankingConstantsSP() const { return _rankingConstants; } + const RankingExpressions::SP &getRankingExpressionsSP() const { return _rankingExpressions; } const OnnxModels::SP &getOnnxModelsSP() const { return _onnxModels; } const IndexschemaConfigSP &getIndexschemaConfigSP() const { return _indexschema; } const AttributesConfigSP &getAttributesConfigSP() const { return _attributes; } diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp index 8d513fde62f..fb154081963 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp @@ -6,6 +6,7 @@ #include <vespa/searchcore/proton/common/alloc_config.h> #include <vespa/searchcore/proton/common/hw_info.h> #include <vespa/searchcore/config/config-ranking-constants.h> +#include <vespa/searchcore/config/config-ranking-expressions.h> #include <vespa/searchcore/config/config-onnx-models.h> #include <vespa/config-imported-fields.h> #include <vespa/config-rank-profiles.h> @@ -20,6 +21,7 @@ #include <vespa/searchlib/index/schemautil.h> #include <vespa/searchsummary/config/config-juniperrc.h> #include <vespa/vespalib/time/time_box.h> +#include <vespa/vespalib/util/stringfmt.h> #include <thread> #include <cassert> @@ -36,6 +38,7 @@ using search::TuneFileDocumentDB; using search::index::Schema; using search::index::SchemaBuilder; using proton::matching::RankingConstants; +using proton::matching::RankingExpressions; using proton::matching::OnnxModels; using vespalib::compression::CompressionConfig; using search::LogDocumentStore; @@ -45,6 +48,8 @@ using search::WriteableFileChunk; using std::make_shared; using std::make_unique; +using vespalib::make_string_short::fmt; + namespace proton { const ConfigKeySet @@ -53,6 +58,7 @@ DocumentDBConfigManager::createConfigKeySet() const ConfigKeySet set; set.add<RankProfilesConfig, RankingConstantsConfig, + RankingExpressionsConfig, OnnxModelsConfig, IndexschemaConfig, AttributesConfig, @@ -264,6 +270,21 @@ build_alloc_config(const ProtonConfig& proton_config, const vespalib::string& do distribution_config.redundancy, distribution_config.searchablecopies); } +vespalib::string resolve_file(config::RpcFileAcquirer &fileAcquirer, vespalib::TimeBox &timeBox, + const vespalib::string &desc, const vespalib::string &fileref) +{ + vespalib::string filePath; + LOG(info, "Waiting for file acquirer (%s, ref='%s')", desc.c_str(), fileref.c_str()); + while (timeBox.hasTimeLeft() && (filePath == "")) { + filePath = fileAcquirer.wait_for(fileref, timeBox.timeLeft()); + if (filePath == "") { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + LOG(info, "Got file path from file acquirer: '%s' (%s, ref='%s')", filePath.c_str(), desc.c_str(), fileref.c_str()); + return filePath; +} + } void @@ -271,6 +292,7 @@ DocumentDBConfigManager::update(const ConfigSnapshot &snapshot) { using RankProfilesConfigSP = DocumentDBConfig::RankProfilesConfigSP; using RankingConstantsConfigSP = std::shared_ptr<vespa::config::search::core::RankingConstantsConfig>; + using RankingExpressionsConfigSP = std::shared_ptr<vespa::config::search::core::RankingExpressionsConfig>; using OnnxModelsConfigSP = std::shared_ptr<vespa::config::search::core::OnnxModelsConfig>; using IndexschemaConfigSP = DocumentDBConfig::IndexschemaConfigSP; using SummaryConfigSP = DocumentDBConfig::SummaryConfigSP; @@ -282,6 +304,7 @@ DocumentDBConfigManager::update(const ConfigSnapshot &snapshot) DocumentDBConfig::SP current = _pendingConfigSnapshot; RankProfilesConfigSP newRankProfilesConfig; matching::RankingConstants::SP newRankingConstants; + matching::RankingExpressions::SP newRankingExpressions; matching::OnnxModels::SP newOnnxModels; IndexschemaConfigSP newIndexschemaConfig; MaintenanceConfigSP oldMaintenanceConfig; @@ -308,6 +331,7 @@ DocumentDBConfigManager::update(const ConfigSnapshot &snapshot) if (current) { newRankProfilesConfig = current->getRankProfilesConfigSP(); newRankingConstants = current->getRankingConstantsSP(); + newRankingExpressions = current->getRankingExpressionsSP(); newOnnxModels = current->getOnnxModelsSP(); newIndexschemaConfig = current->getIndexschemaConfigSP(); oldMaintenanceConfig = current->getMaintenanceConfigSP(); @@ -328,22 +352,29 @@ DocumentDBConfigManager::update(const ConfigSnapshot &snapshot) config::RpcFileAcquirer fileAcquirer(spec); vespalib::TimeBox timeBox(5*60, 5); for (const RankingConstantsConfig::Constant &rc : newRankingConstantsConfig->constant) { - vespalib::string filePath; - LOG(info, "Waiting for file acquirer (name='%s', type='%s', ref='%s')", - rc.name.c_str(), rc.type.c_str(), rc.fileref.c_str()); - while (timeBox.hasTimeLeft() && (filePath == "")) { - filePath = fileAcquirer.wait_for(rc.fileref, timeBox.timeLeft()); - if (filePath == "") { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - LOG(info, "Got file path from file acquirer: '%s' (name='%s', type='%s', ref='%s')", - filePath.c_str(), rc.name.c_str(), rc.type.c_str(), rc.fileref.c_str()); + auto desc = fmt("name='%s', type='%s'", rc.name.c_str(), rc.type.c_str()); + vespalib::string filePath = resolve_file(fileAcquirer, timeBox, desc, rc.fileref); constants.emplace_back(rc.name, rc.type, filePath); } } newRankingConstants = std::make_shared<RankingConstants>(constants); } + if (snapshot.isChanged<RankingExpressionsConfig>(_configId, currentGeneration)) { + RankingExpressionsConfigSP newRankingExpressionsConfig = RankingExpressionsConfigSP( + snapshot.getConfig<RankingExpressionsConfig>(_configId)); + const vespalib::string &spec = _bootstrapConfig->getFiledistributorrpcConfig().connectionspec; + RankingExpressions expressions; + if (spec != "") { + config::RpcFileAcquirer fileAcquirer(spec); + vespalib::TimeBox timeBox(5*60, 5); + for (const RankingExpressionsConfig::Expression &rc : newRankingExpressionsConfig->expression) { + auto desc = fmt("name='%s'", rc.name.c_str()); + vespalib::string filePath = resolve_file(fileAcquirer, timeBox, desc, rc.fileref); + expressions.add(rc.name, filePath); + } + } + newRankingExpressions = std::make_shared<RankingExpressions>(std::move(expressions)); + } if (snapshot.isChanged<OnnxModelsConfig>(_configId, currentGeneration)) { OnnxModelsConfigSP newOnnxModelsConfig = OnnxModelsConfigSP( snapshot.getConfig<OnnxModelsConfig>(_configId)); @@ -353,17 +384,8 @@ DocumentDBConfigManager::update(const ConfigSnapshot &snapshot) config::RpcFileAcquirer fileAcquirer(spec); vespalib::TimeBox timeBox(5*60, 5); for (const OnnxModelsConfig::Model &rc : newOnnxModelsConfig->model) { - vespalib::string filePath; - LOG(info, "Waiting for file acquirer (name='%s', ref='%s')", - rc.name.c_str(), rc.fileref.c_str()); - while (timeBox.hasTimeLeft() && (filePath == "")) { - filePath = fileAcquirer.wait_for(rc.fileref, timeBox.timeLeft()); - if (filePath == "") { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - LOG(info, "Got file path from file acquirer: '%s' (name='%s', ref='%s')", - filePath.c_str(), rc.name.c_str(), rc.fileref.c_str()); + auto desc = fmt("name='%s'", rc.name.c_str()); + vespalib::string filePath = resolve_file(fileAcquirer, timeBox, desc, rc.fileref); models.emplace_back(rc.name, filePath); OnnxModels::configure(rc, models.back()); } @@ -403,6 +425,7 @@ DocumentDBConfigManager::update(const ConfigSnapshot &snapshot) auto newSnapshot = std::make_shared<DocumentDBConfig>(generation, newRankProfilesConfig, newRankingConstants, + newRankingExpressions, newOnnxModels, newIndexschemaConfig, filterImportedAttributes(newAttributesConfig), diff --git a/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp index 9f67058a2d7..4b862b40896 100644 --- a/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp @@ -327,6 +327,7 @@ FileConfigManager::loadConfig(const DocumentDBConfig ¤tSnapshot, config::DirSpec spec(snapDir); addEmptyFile(snapDir, "ranking-constants.cfg"); + addEmptyFile(snapDir, "ranking-expressions.cfg"); addEmptyFile(snapDir, "onnx-models.cfg"); addEmptyFile(snapDir, "imported-fields.cfg"); diff --git a/searchcore/src/vespa/searchcore/proton/server/matchers.cpp b/searchcore/src/vespa/searchcore/proton/server/matchers.cpp index 53c96a81134..7288e3e98fb 100644 --- a/searchcore/src/vespa/searchcore/proton/server/matchers.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/matchers.cpp @@ -2,11 +2,13 @@ #include "matchers.h" #include <vespa/searchcore/proton/matching/matcher.h> +#include <vespa/searchcore/proton/matching/ranking_expressions.h> #include <vespa/searchcore/proton/matching/onnx_models.h> #include <vespa/vespalib/stllike/hash_map.hpp> namespace proton { +using matching::RankingExpressions; using matching::OnnxModels; Matchers::Matchers(const vespalib::Clock &clock, @@ -14,7 +16,7 @@ Matchers::Matchers(const vespalib::Clock &clock, const matching::IConstantValueRepo &constantValueRepo) : _rpmap(), _fallback(new matching::Matcher(search::index::Schema(), search::fef::Properties(), - clock, queryLimiter, constantValueRepo, OnnxModels(), -1)), + clock, queryLimiter, constantValueRepo, RankingExpressions(), OnnxModels(), -1)), _default() { } diff --git a/searchcore/src/vespa/searchcore/proton/server/pruneremoveddocumentsjob.h b/searchcore/src/vespa/searchcore/proton/server/pruneremoveddocumentsjob.h index 9216b10e5a9..637c341ff6a 100644 --- a/searchcore/src/vespa/searchcore/proton/server/pruneremoveddocumentsjob.h +++ b/searchcore/src/vespa/searchcore/proton/server/pruneremoveddocumentsjob.h @@ -15,7 +15,7 @@ namespace proton { struct IDocumentMetaStore; class IPruneRemovedDocumentsHandler; -class RawDocumentMetaData; +struct RawDocumentMetaData; /** * Job that regularly checks whether old removed documents should be diff --git a/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp b/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp index bb6c5423175..7cdc523e381 100644 --- a/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/reconfig_params.cpp @@ -15,6 +15,7 @@ ReconfigParams::configHasChanged() const { return _res.rankProfilesChanged || _res.rankingConstantsChanged || + _res.rankingExpressionsChanged || _res.onnxModelsChanged || _res.indexschemaChanged || _res.attributesChanged || @@ -40,7 +41,7 @@ ReconfigParams::shouldSchemaChange() const bool ReconfigParams::shouldMatchersChange() const { - return _res.rankProfilesChanged || _res.rankingConstantsChanged || _res.onnxModelsChanged || shouldSchemaChange(); + return _res.rankProfilesChanged || _res.rankingConstantsChanged || _res.rankingExpressionsChanged || _res.onnxModelsChanged || shouldSchemaChange(); } bool diff --git a/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.cpp b/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.cpp index 1e5c558af0b..05e68ee0ec4 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.cpp @@ -21,6 +21,7 @@ using vespa::config::search::RankProfilesConfig; namespace proton { using matching::Matcher; +using matching::RankingExpressions; using matching::OnnxModels; typedef AttributeReprocessingInitializer::Config ARIConfig; @@ -107,6 +108,7 @@ SearchableDocSubDBConfigurer::~SearchableDocSubDBConfigurer() = default; Matchers::UP SearchableDocSubDBConfigurer::createMatchers(const Schema::SP &schema, const RankProfilesConfig &cfg, + const RankingExpressions &rankingExpressions, const OnnxModels &onnxModels) { auto newMatchers = std::make_unique<Matchers>(_clock, _queryLimiter, _constantValueRepo); @@ -117,7 +119,8 @@ SearchableDocSubDBConfigurer::createMatchers(const Schema::SP &schema, properties.add(property.name, property.value); } // schema instance only used during call. - auto profptr = std::make_shared<Matcher>(*schema, properties, _clock, _queryLimiter, _constantValueRepo, onnxModels, _distributionKey); + auto profptr = std::make_shared<Matcher>(*schema, properties, _clock, _queryLimiter, _constantValueRepo, + rankingExpressions, onnxModels, _distributionKey); newMatchers->add(name, profptr); } return newMatchers; @@ -185,6 +188,7 @@ SearchableDocSubDBConfigurer::reconfigure(const DocumentDBConfig &newConfig, _constantValueRepo.reconfigure(newConfig.getRankingConstants()); Matchers::SP newMatchers = createMatchers(newConfig.getSchemaSP(), newConfig.getRankProfilesConfig(), + newConfig.getRankingExpressions(), newConfig.getOnnxModels()); matchers = newMatchers; shouldMatchViewChange = true; diff --git a/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.h b/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.h index d460bb6506f..1a9ba1abff7 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.h +++ b/searchcore/src/vespa/searchcore/proton/server/searchable_doc_subdb_configurer.h @@ -74,6 +74,7 @@ public: Matchers::UP createMatchers(const search::index::Schema::SP &schema, const vespa::config::search::RankProfilesConfig &cfg, + const proton::matching::RankingExpressions &rankingExpressions, const proton::matching::OnnxModels &onnxModels); void reconfigureIndexSearchable(); diff --git a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp index 51e6f8e45df..d7aaa901530 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/searchabledocsubdb.cpp @@ -201,7 +201,8 @@ SearchableDocSubDB::initViews(const DocumentDBConfig &configSnapshot, const Sess const Schema::SP &schema = configSnapshot.getSchemaSP(); const IIndexManager::SP &indexMgr = getIndexManager(); _constantValueRepo.reconfigure(configSnapshot.getRankingConstants()); - Matchers::SP matchers = _configurer.createMatchers(schema, configSnapshot.getRankProfilesConfig(), configSnapshot.getOnnxModels()); + Matchers::SP matchers = _configurer.createMatchers(schema, configSnapshot.getRankProfilesConfig(), + configSnapshot.getRankingExpressions(), configSnapshot.getOnnxModels()); auto matchView = std::make_shared<MatchView>(std::move(matchers), indexMgr->getSearchable(), attrMgr, sessionManager, _metaStoreCtx, _docIdLimit); _rSearchView.set(SearchView::create( diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp b/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp index c050719b361..de3baa8d26e 100644 --- a/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp @@ -325,11 +325,10 @@ StoreOnlyFeedView::putSummary(SerialNum serialNum, Lid lid, } void -StoreOnlyFeedView::putSummaryNoop(SerialNum serialNum, Lid lid, - FutureStream futureStream, OnOperationDoneType onDone) +StoreOnlyFeedView::putSummaryNoop(FutureStream futureStream, OnOperationDoneType onDone) { summaryExecutor().execute( - makeLambdaTask([serialNum, lid, futureStream = std::move(futureStream), onDone] () mutable { + makeLambdaTask([futureStream = std::move(futureStream), onDone] () mutable { (void) onDone; vespalib::nbostream os = futureStream.get(); (void) os; @@ -427,7 +426,7 @@ StoreOnlyFeedView::internalUpdate(FeedToken token, const UpdateOperation &updOp) if (useDocStore) { putSummary(serialNum, lid, std::move(futureStream), onWriteDone); } else { - putSummaryNoop(serialNum, lid, std::move(futureStream), onWriteDone); + putSummaryNoop(std::move(futureStream), onWriteDone); } _writeService.shared().execute(makeLambdaTask( [upd = updOp.getUpdate(), useDocStore, lid, onWriteDone, promisedDoc = std::move(promisedDoc), diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.h b/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.h index 5a0257fda57..b9e02276180 100644 --- a/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.h +++ b/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.h @@ -157,7 +157,7 @@ private: return _writeService.summary(); } void putSummary(SerialNum serialNum, Lid lid, FutureStream doc, OnOperationDoneType onDone); - void putSummaryNoop(SerialNum serialNum, Lid lid, FutureStream doc, OnOperationDoneType onDone); + void putSummaryNoop(FutureStream doc, OnOperationDoneType onDone); void putSummary(SerialNum serialNum, Lid lid, DocumentSP doc, OnOperationDoneType onDone); void removeSummary(SerialNum serialNum, Lid lid, OnWriteDoneType onDone); void heartBeatSummary(SerialNum serialNum); diff --git a/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.cpp b/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.cpp index 17cb91ac4ce..18774e60202 100644 --- a/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.cpp +++ b/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.cpp @@ -34,6 +34,7 @@ DocumentDBConfigBuilder::DocumentDBConfigBuilder(int64_t generation, : _generation(generation), _rankProfiles(std::make_shared<RankProfilesConfig>()), _rankingConstants(std::make_shared<matching::RankingConstants>()), + _rankingExpressions(std::make_shared<matching::RankingExpressions>()), _onnxModels(std::make_shared<matching::OnnxModels>()), _indexschema(std::make_shared<IndexschemaConfig>()), _attributes(std::make_shared<AttributesConfig>()), @@ -58,6 +59,7 @@ DocumentDBConfigBuilder::DocumentDBConfigBuilder(const DocumentDBConfig &cfg) : _generation(cfg.getGeneration()), _rankProfiles(cfg.getRankProfilesConfigSP()), _rankingConstants(cfg.getRankingConstantsSP()), + _rankingExpressions(cfg.getRankingExpressionsSP()), _onnxModels(cfg.getOnnxModelsSP()), _indexschema(cfg.getIndexschemaConfigSP()), _attributes(cfg.getAttributesConfigSP()), @@ -86,6 +88,7 @@ DocumentDBConfigBuilder::build() _generation, _rankProfiles, _rankingConstants, + _rankingExpressions, _onnxModels, _indexschema, _attributes, diff --git a/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.h b/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.h index 706e14e73db..a248ed5544a 100644 --- a/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.h +++ b/searchcore/src/vespa/searchcore/proton/test/documentdb_config_builder.h @@ -14,6 +14,7 @@ private: int64_t _generation; DocumentDBConfig::RankProfilesConfigSP _rankProfiles; DocumentDBConfig::RankingConstants::SP _rankingConstants; + DocumentDBConfig::RankingExpressions::SP _rankingExpressions; DocumentDBConfig::OnnxModels::SP _onnxModels; DocumentDBConfig::IndexschemaConfigSP _indexschema; DocumentDBConfig::AttributesConfigSP _attributes; @@ -57,6 +58,10 @@ public: _rankingConstants = rankingConstants_in; return *this; } + DocumentDBConfigBuilder &rankingExpressions(const DocumentDBConfig::RankingExpressions::SP &rankingExpressions_in) { + _rankingExpressions = rankingExpressions_in; + return *this; + } DocumentDBConfigBuilder &onnxModels(const DocumentDBConfig::OnnxModels::SP &onnxModels_in) { _onnxModels = onnxModels_in; return *this; diff --git a/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp b/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp index 251040ecfa7..6baa6581edf 100644 --- a/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp +++ b/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp @@ -67,28 +67,38 @@ struct SetupResult { RankingExpressionBlueprint rank; DummyDependencyHandler deps; bool setup_ok; - SetupResult(const TypeMap &object_inputs, const vespalib::string &expression); + SetupResult(const TypeMap &object_inputs, const vespalib::string &expression, + bool external_expression = false); ~SetupResult(); }; SetupResult::SetupResult(const TypeMap &object_inputs, - const vespalib::string &expression) + const vespalib::string &expression, + bool external_expression) : stash(), index_env(), query_env(&index_env), rank(make_replacer()), deps(rank), setup_ok(false) { rank.setName("self"); - index_env.getProperties().add("self.rankingScript", expression); for (const auto &input: object_inputs) { deps.define_object_input(input.first, ValueType::from_spec(input.second)); } - setup_ok = rank.setup(index_env, {}); + std::vector<vespalib::string> params; + if (external_expression) { + params.push_back("my_expr"); + index_env.addRankingExpression("my_expr", expression); + } else { + index_env.getProperties().add("self.rankingScript", expression); + } + Blueprint &bp = rank; + setup_ok = bp.setup(index_env, params); EXPECT_TRUE(!deps.accept_type_mismatch); } SetupResult::~SetupResult() = default; void verify_output_type(const TypeMap &object_inputs, - const vespalib::string &expression, const FeatureType &expect) + const vespalib::string &expression, const FeatureType &expect, + bool external_expression = false) { - SetupResult result(object_inputs, expression); + SetupResult result(object_inputs, expression, external_expression); EXPECT_TRUE(result.setup_ok); EXPECT_EQUAL(1u, result.deps.output.size()); ASSERT_EQUAL(1u, result.deps.output_type.size()); @@ -126,6 +136,13 @@ TEST("require that ranking expression can resolve to concrete complex type") { FeatureType::object(ValueType::from_spec("tensor(x{},y{},z{})")))); } +TEST("require that ranking expression can be external") { + TEST_DO(verify_output_type({}, "a*b", FeatureType::number(), true)); + TEST_DO(verify_output_type({{"b", "double"}}, "a*b", FeatureType::object(ValueType::double_type()), true)); + TEST_DO(verify_output_type({{"a", "tensor(x{},y{})"}, {"b", "tensor(y{},z{})"}}, "a*b", + FeatureType::object(ValueType::from_spec("tensor(x{},y{},z{})")), true)); +} + TEST("require that setup fails for incompatible types") { TEST_DO(verify_setup_fail({{"a", "tensor(x{},y{})"}, {"b", "tensor(y[10],z{})"}}, "a*b")); } diff --git a/searchlib/src/vespa/searchlib/features/rankingexpressionfeature.cpp b/searchlib/src/vespa/searchlib/features/rankingexpressionfeature.cpp index 8cba6013627..070d70997e6 100644 --- a/searchlib/src/vespa/searchlib/features/rankingexpressionfeature.cpp +++ b/searchlib/src/vespa/searchlib/features/rankingexpressionfeature.cpp @@ -250,7 +250,10 @@ RankingExpressionBlueprint::setup(const fef::IIndexEnvironment &env, } //LOG(debug, "Script from config: '%s'\n", script.c_str()); } else if (params.size() == 1) { - script = params[0].getValue(); + script = env.getRankingExpression(params[0].getValue()); + if (script.empty()) { + script = params[0].getValue(); + } //LOG(debug, "Script from param: '%s'\n", script.c_str()); } else { return fail("No expression given."); diff --git a/searchlib/src/vespa/searchlib/fef/iindexenvironment.h b/searchlib/src/vespa/searchlib/fef/iindexenvironment.h index 0cd017d8ffa..a01d7d8762b 100644 --- a/searchlib/src/vespa/searchlib/fef/iindexenvironment.h +++ b/searchlib/src/vespa/searchlib/fef/iindexenvironment.h @@ -123,6 +123,11 @@ public: virtual std::unique_ptr<vespalib::eval::ConstantValue> getConstantValue(const vespalib::string &name) const = 0; /** + * Returns the ranking expression with the given name or empty string if not found. + **/ + virtual vespalib::string getRankingExpression(const vespalib::string &name) const = 0; + + /** * Get configuration for the given onnx model. **/ virtual const OnnxModel *getOnnxModel(const vespalib::string &name) const = 0; diff --git a/searchlib/src/vespa/searchlib/fef/test/indexenvironment.cpp b/searchlib/src/vespa/searchlib/fef/test/indexenvironment.cpp index d2d336dcdc8..85b20710f7a 100644 --- a/searchlib/src/vespa/searchlib/fef/test/indexenvironment.cpp +++ b/searchlib/src/vespa/searchlib/fef/test/indexenvironment.cpp @@ -54,6 +54,22 @@ IndexEnvironment::addConstantValue(const vespalib::string &name, (void) insertRes; } +vespalib::string +IndexEnvironment::getRankingExpression(const vespalib::string &name) const +{ + auto pos = _expressions.find(name); + if (pos != _expressions.end()) { + return pos->second; + } + return {}; +} + +void +IndexEnvironment::addRankingExpression(const vespalib::string &name, const vespalib::string &value) +{ + _expressions.insert_or_assign(name, value); +} + const OnnxModel * IndexEnvironment::getOnnxModel(const vespalib::string &name) const { diff --git a/searchlib/src/vespa/searchlib/fef/test/indexenvironment.h b/searchlib/src/vespa/searchlib/fef/test/indexenvironment.h index 0d8d0091921..ca63117971a 100644 --- a/searchlib/src/vespa/searchlib/fef/test/indexenvironment.h +++ b/searchlib/src/vespa/searchlib/fef/test/indexenvironment.h @@ -48,6 +48,7 @@ public: }; using ConstantsMap = std::map<vespalib::string, Constant>; + using ExprMap = std::map<vespalib::string, vespalib::string>; using ModelMap = std::map<vespalib::string, OnnxModel>; IndexEnvironment(); @@ -85,6 +86,9 @@ public: vespalib::eval::ValueType type, std::unique_ptr<vespalib::eval::Value> value); + vespalib::string getRankingExpression(const vespalib::string &name) const override; + void addRankingExpression(const vespalib::string &name, const vespalib::string &value); + const OnnxModel *getOnnxModel(const vespalib::string &name) const override; void addOnnxModel(const OnnxModel &model); @@ -98,6 +102,7 @@ private: AttributeMap _attrMap; TableManager _tableMan; ConstantsMap _constants; + ExprMap _expressions; ModelMap _models; }; diff --git a/slobrok/src/vespa/slobrok/cfg.cpp b/slobrok/src/vespa/slobrok/cfg.cpp index 03b2b12803e..01da82b41c1 100644 --- a/slobrok/src/vespa/slobrok/cfg.cpp +++ b/slobrok/src/vespa/slobrok/cfg.cpp @@ -34,7 +34,6 @@ Configurator::Configurator(Configurable& target, const config::ConfigUri & uri) : _subscriber(uri.getContext()), _handle(_subscriber.subscribe<cloud::config::SlobroksConfig>(uri.getConfigId())), _target(target) - { } diff --git a/slobrok/src/vespa/slobrok/sbmirror.cpp b/slobrok/src/vespa/slobrok/sbmirror.cpp index 6aff47e13fd..13680d16e68 100644 --- a/slobrok/src/vespa/slobrok/sbmirror.cpp +++ b/slobrok/src/vespa/slobrok/sbmirror.cpp @@ -3,6 +3,7 @@ #include "sbmirror.h" #include <vespa/fnet/frt/supervisor.h> #include <vespa/fnet/frt/target.h> +#include <vespa/vespalib/util/exceptions.h> #include <vespa/log/log.h> LOG_SETUP(".slobrok.mirror"); @@ -30,7 +31,9 @@ MirrorAPI::MirrorAPI(FRT_Supervisor &orb, const ConfiguratorFactory & config) _req(0) { _configurator->poll(); - LOG_ASSERT(_slobrokSpecs.ok()); + if (!_slobrokSpecs.ok()) { + throw vespalib::IllegalStateException("Not able to initialize MirrorAPI due to missing or bad slobrok specs"); + } ScheduleNow(); } diff --git a/storage/src/tests/distributor/distributor_stripe_pool_test.cpp b/storage/src/tests/distributor/distributor_stripe_pool_test.cpp index fb7c446a781..bf3beda504e 100644 --- a/storage/src/tests/distributor/distributor_stripe_pool_test.cpp +++ b/storage/src/tests/distributor/distributor_stripe_pool_test.cpp @@ -73,7 +73,7 @@ struct ParkingInvariantCheckingMockStripe : MockTickableStripe { TEST_F(DistributorStripePoolThreadingTest, can_park_and_unpark_single_stripe) { ParkingInvariantCheckingMockStripe stripe(_is_parked); - _pool.start({{&stripe}}); + _pool.start({&stripe}); loop_park_unpark_cycle_until_test_time_expired(); _pool.stop_and_join(); } diff --git a/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h b/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h index 41878f09014..ec7b7791c11 100644 --- a/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h +++ b/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h @@ -56,9 +56,9 @@ public: } }; - typedef ConstIterator const_iterator; + using const_iterator = ConstIterator; - virtual ~BucketPriorityDatabase() { } + virtual ~BucketPriorityDatabase() = default; virtual const_iterator begin() const = 0; diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenanceoperation.h b/storage/src/vespa/storage/distributor/maintenance/maintenanceoperation.h index f9fbc4a2be1..a1cdaf57119 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenanceoperation.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenanceoperation.h @@ -3,13 +3,12 @@ #include <vespa/storage/distributor/operations/operation.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenanceOperation : public Operation { public: - typedef enum { + enum Type { DELETE_BUCKET, MERGE_BUCKET, SPLIT_BUCKET, @@ -17,12 +16,11 @@ public: SET_BUCKET_STATE, GARBAGE_COLLECTION, OPERATION_COUNT - } Type; + }; - typedef std::shared_ptr<MaintenanceOperation> SP; + using SP = std::shared_ptr<MaintenanceOperation>; virtual const std::string& getDetailedReason() const = 0; }; -} // distributor -} // storage +} // storage::distributor diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenanceoperationgenerator.h b/storage/src/vespa/storage/distributor/maintenance/maintenanceoperationgenerator.h index 1afc0990222..3cf6ad33694 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenanceoperationgenerator.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenanceoperationgenerator.h @@ -5,13 +5,12 @@ #include <vespa/storage/distributor/maintenance/maintenanceoperation.h> #include <vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenanceOperationGenerator { public: - virtual ~MaintenanceOperationGenerator() {} + virtual ~MaintenanceOperationGenerator() = default; /** * Generate and return the highest prioritized maintenance operation for @@ -40,5 +39,4 @@ public: } }; -} // distributor -} // storage +} // storage::distributor diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenancepriority.h b/storage/src/vespa/storage/distributor/maintenance/maintenancepriority.h index 11bde9b1986..ad82022b216 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenancepriority.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenancepriority.h @@ -1,8 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenancePriority { @@ -18,7 +17,7 @@ public: PRIORITY_LIMIT }; - static const std::string toString(Priority pri) { + static constexpr const char* toString(Priority pri) noexcept { switch (pri) { case NO_MAINTENANCE_NEEDED: return "NO_MAINTENANCE_NEEDED"; case VERY_LOW: return "VERY_LOW"; @@ -56,6 +55,3 @@ private: }; } -} - - diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenancepriorityandtype.h b/storage/src/vespa/storage/distributor/maintenance/maintenancepriorityandtype.h index 9335f053b4c..4e272bf03cc 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenancepriorityandtype.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenancepriorityandtype.h @@ -4,8 +4,7 @@ #include <vespa/storage/distributor/maintenance/maintenanceoperation.h> #include <vespa/storage/distributor/maintenance/maintenancepriority.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenancePriorityAndType { @@ -32,5 +31,4 @@ public: } }; -} // distributor -} // storage +} // storage::distributor diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenanceprioritygenerator.h b/storage/src/vespa/storage/distributor/maintenance/maintenanceprioritygenerator.h index ca7fb6ae81f..bf32fd4ccc1 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenanceprioritygenerator.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenanceprioritygenerator.h @@ -6,13 +6,12 @@ #include <vespa/storage/distributor/maintenance/maintenancepriorityandtype.h> #include <vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenancePriorityGenerator { public: - virtual ~MaintenancePriorityGenerator() {} + virtual ~MaintenancePriorityGenerator() = default; virtual MaintenancePriorityAndType prioritize( const document::Bucket &bucket, @@ -20,5 +19,3 @@ public: }; } -} - diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenancescanner.h b/storage/src/vespa/storage/distributor/maintenance/maintenancescanner.h index f329133f2c4..412fe91d690 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenancescanner.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenancescanner.h @@ -4,13 +4,12 @@ #include <vespa/document/bucket/bucketspace.h> #include <vespa/storage/bucketdb/bucketdatabase.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenanceScanner { public: - virtual ~MaintenanceScanner() {} + virtual ~MaintenanceScanner() = default; class ScanResult { bool _done; @@ -38,5 +37,3 @@ public: }; } -} - diff --git a/storage/src/vespa/storage/distributor/maintenance/maintenancescheduler.h b/storage/src/vespa/storage/distributor/maintenance/maintenancescheduler.h index 7d17e42b934..acb0c555f49 100644 --- a/storage/src/vespa/storage/distributor/maintenance/maintenancescheduler.h +++ b/storage/src/vespa/storage/distributor/maintenance/maintenancescheduler.h @@ -5,8 +5,7 @@ #include <vespa/storage/distributor/maintenance/simplemaintenancescanner.h> #include <vespa/storage/distributor/operationstarter.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class MaintenanceOperationGenerator; class BucketPriorityDatabase; @@ -19,7 +18,7 @@ public: NORMAL_SCHEDULING_MODE }; - typedef int WaitTimeMs; + using WaitTimeMs = int; MaintenanceScheduler(MaintenanceOperationGenerator& operationGenerator, BucketPriorityDatabase& priorityDb, @@ -31,7 +30,6 @@ private: MaintenanceScheduler(const MaintenanceScheduler&); MaintenanceScheduler& operator=(const MaintenanceScheduler&); - //void scanNextBucket(); PrioritizedBucket getMostImportantBucket(); bool possibleToSchedule(const PrioritizedBucket& bucket, SchedulingMode currentMode) const; bool possibleToScheduleInEmergency(const PrioritizedBucket& bucket) const; @@ -46,5 +44,3 @@ private: }; } -} - diff --git a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp index 9d999681407..b954ef93c76 100644 --- a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp +++ b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp @@ -20,8 +20,8 @@ operator<<(std::ostream& os, const NodeMaintenanceStats& stats) return os; } -NodeMaintenanceStatsTracker::NodeMaintenanceStatsTracker() {} -NodeMaintenanceStatsTracker::~NodeMaintenanceStatsTracker() {} +NodeMaintenanceStatsTracker::NodeMaintenanceStatsTracker() = default; +NodeMaintenanceStatsTracker::~NodeMaintenanceStatsTracker() = default; } diff --git a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h index 296c6f76563..faf253fc84c 100644 --- a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h +++ b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h @@ -6,8 +6,7 @@ #include <stdint.h> #include <vespa/document/bucket/bucketspace.h> -namespace storage { -namespace distributor { +namespace storage::distributor { struct NodeMaintenanceStats { @@ -17,12 +16,15 @@ struct NodeMaintenanceStats uint64_t copyingOut; uint64_t total; - NodeMaintenanceStats() + constexpr NodeMaintenanceStats() noexcept : movingOut(0), syncing(0), copyingIn(0), copyingOut(0), total(0) {} - NodeMaintenanceStats(uint64_t movingOut_, uint64_t syncing_, uint64_t copyingIn_, uint64_t copyingOut_, uint64_t total_) - : movingOut(movingOut_), syncing(syncing_), copyingIn(copyingIn_), copyingOut(copyingOut_), total(total_) + constexpr NodeMaintenanceStats(uint64_t movingOut_, uint64_t syncing_, uint64_t copyingIn_, + uint64_t copyingOut_, uint64_t total_) noexcept + : movingOut(movingOut_), syncing(syncing_), + copyingIn(copyingIn_), copyingOut(copyingOut_), + total(total_) {} bool operator==(const NodeMaintenanceStats& other) const noexcept { @@ -52,6 +54,7 @@ private: public: NodeMaintenanceStatsTracker(); ~NodeMaintenanceStatsTracker(); + void incMovingOut(uint16_t node, document::BucketSpace bucketSpace) { ++_stats[node][bucketSpace].movingOut; } @@ -92,6 +95,4 @@ public: } }; -} // distributor -} // storage - +} // storage::distributor diff --git a/storage/src/vespa/storage/distributor/maintenance/prioritizedbucket.h b/storage/src/vespa/storage/distributor/maintenance/prioritizedbucket.h index fde73de0358..d6d68a956be 100644 --- a/storage/src/vespa/storage/distributor/maintenance/prioritizedbucket.h +++ b/storage/src/vespa/storage/distributor/maintenance/prioritizedbucket.h @@ -6,13 +6,11 @@ #include <vespa/vespalib/util/stringfmt.h> #include <vespa/storage/distributor/maintenance/maintenancepriority.h> -namespace storage { - -namespace distributor { +namespace storage::distributor { class PrioritizedBucket { public: - typedef MaintenancePriority::Priority Priority; + using Priority = MaintenancePriority::Priority; static const PrioritizedBucket INVALID; @@ -40,7 +38,7 @@ public: std::string toString() const { return vespalib::make_string("PrioritizedBucket(%s, pri %s)", _bucket.toString().c_str(), - MaintenancePriority::toString(_priority).c_str()); + MaintenancePriority::toString(_priority)); } bool operator==(const PrioritizedBucket& other) const { @@ -68,6 +66,3 @@ std::ostream& operator<<(std::ostream& os, const PrioritizedBucket& bucket); } - -} - diff --git a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp index 7b75091e252..fa1a632d355 100644 --- a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp +++ b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp @@ -6,9 +6,7 @@ namespace storage::distributor { -SimpleBucketPriorityDatabase::~SimpleBucketPriorityDatabase() -{ -} +SimpleBucketPriorityDatabase::~SimpleBucketPriorityDatabase() = default; void SimpleBucketPriorityDatabase::clearAllEntriesForBucket(const document::Bucket &bucket) diff --git a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h index 70b007decb6..db02c248980 100644 --- a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h +++ b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h @@ -5,14 +5,13 @@ #include <set> #include <map> -namespace storage { -namespace distributor { +namespace storage::distributor { class SimpleBucketPriorityDatabase : public BucketPriorityDatabase { public: virtual ~SimpleBucketPriorityDatabase(); - typedef PrioritizedBucket::Priority Priority; + using Priority = PrioritizedBucket::Priority; virtual void setPriority(const PrioritizedBucket&) override; virtual const_iterator begin() const override; @@ -21,8 +20,8 @@ public: std::string toString() const; private: - typedef std::set<document::Bucket> BucketSet; - typedef std::map<Priority, BucketSet> PriorityMap; + using BucketSet = std::set<document::Bucket>; + using PriorityMap = std::map<Priority, BucketSet>; class SimpleConstIteratorImpl : public ConstIteratorImpl { @@ -62,4 +61,3 @@ private: }; } -} diff --git a/storage/src/vespa/storage/distributor/maintenance/simplemaintenancescanner.h b/storage/src/vespa/storage/distributor/maintenance/simplemaintenancescanner.h index 5b316201036..254b3244171 100644 --- a/storage/src/vespa/storage/distributor/maintenance/simplemaintenancescanner.h +++ b/storage/src/vespa/storage/distributor/maintenance/simplemaintenancescanner.h @@ -7,8 +7,7 @@ #include "node_maintenance_stats_tracker.h" #include <vespa/storage/distributor/distributor_bucket_space_repo.h> -namespace storage { -namespace distributor { +namespace storage::distributor { class SimpleMaintenanceScanner : public MaintenanceScanner { @@ -31,7 +30,7 @@ public: private: BucketPriorityDatabase& _bucketPriorityDb; const MaintenancePriorityGenerator& _priorityGenerator; - const DistributorBucketSpaceRepo &_bucketSpaceRepo; + const DistributorBucketSpaceRepo& _bucketSpaceRepo; DistributorBucketSpaceRepo::BucketSpaceMap::const_iterator _bucketSpaceItr; document::BucketId _bucketCursor; PendingMaintenanceStats _pendingMaintenance; @@ -60,4 +59,3 @@ std::ostream& operator<<(std::ostream&, const SimpleMaintenanceScanner::GlobalMaintenanceStats&); } -} diff --git a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.cpp b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.cpp index 42d9e3d4a3d..142ff72bc79 100644 --- a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.cpp +++ b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.cpp @@ -185,6 +185,9 @@ public: } }; +// TODO STRIPE replace with check for pending cluster state transition. +// Null-bucket messages are not intercepted nor observeable by stripes, +// only by the top-level distributor. bool checkNullBucketRequestBucketInfoMessage(uint16_t node, document::BucketSpace bucketSpace, diff --git a/storage/src/vespa/storage/distributor/operations/operation.h b/storage/src/vespa/storage/distributor/operations/operation.h index 75d72a2b5c9..5099762fd6a 100644 --- a/storage/src/vespa/storage/distributor/operations/operation.h +++ b/storage/src/vespa/storage/distributor/operations/operation.h @@ -5,7 +5,6 @@ #include <vespa/storage/distributor/distributormessagesender.h> #include <vespa/storageframework/generic/clock/time.h> - namespace storage { namespace api { diff --git a/storage/src/vespa/storage/distributor/statechecker.cpp b/storage/src/vespa/storage/distributor/statechecker.cpp index 82b8371e163..049e39fa3fc 100644 --- a/storage/src/vespa/storage/distributor/statechecker.cpp +++ b/storage/src/vespa/storage/distributor/statechecker.cpp @@ -79,6 +79,7 @@ StateChecker::Context::Context(const DistributorNodeContext& node_ctx_in, db(distributorBucketSpace.getBucketDatabase()), stats(statsTracker) { + // TODO STRIPE use existing cache for computing ideal storage nodes for bucket idealState = distribution.getIdealStorageNodes(systemState, bucket.getBucketId()); unorderedIdealState.insert(idealState.begin(), idealState.end()); } diff --git a/storage/src/vespa/storage/persistence/mergehandler.cpp b/storage/src/vespa/storage/persistence/mergehandler.cpp index 6d580ead659..004a6a798a9 100644 --- a/storage/src/vespa/storage/persistence/mergehandler.cpp +++ b/storage/src/vespa/storage/persistence/mergehandler.cpp @@ -1127,32 +1127,6 @@ MergeHandler::handleGetBucketDiff(api::GetBucketDiffCommand& cmd, MessageTracker return tracker; } -namespace { - - struct DiffInfoTimestampOrder - : public std::binary_function<api::GetBucketDiffCommand::Entry, - api::GetBucketDiffCommand::Entry, bool> - { - bool operator()(const api::GetBucketDiffCommand::Entry& x, - const api::GetBucketDiffCommand::Entry& y) - { - return (x._timestamp < y._timestamp); - } - }; - - struct ApplyDiffInfoTimestampOrder - : public std::binary_function<api::ApplyBucketDiffCommand::Entry, - api::ApplyBucketDiffCommand::Entry, bool> - { - bool operator()(const api::ApplyBucketDiffCommand::Entry& x, - const api::ApplyBucketDiffCommand::Entry& y) - { - return (x._entry._timestamp < y._entry._timestamp); - } - }; - -} // End of anonymous namespace - void MergeHandler::handleGetBucketDiffReply(api::GetBucketDiffReply& reply, MessageSender& sender) const { diff --git a/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h b/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h index dc7be36c290..7bc706f21ce 100644 --- a/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h +++ b/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h @@ -73,6 +73,10 @@ public: return vespalib::eval::ConstantValue::UP(); } + vespalib::string getRankingExpression(const vespalib::string &) const override { + return {}; + } + const search::fef::OnnxModel *getOnnxModel(const vespalib::string &) const override { return nullptr; } diff --git a/vespa-feed-client-cli/CMakeLists.txt b/vespa-feed-client-cli/CMakeLists.txt new file mode 100644 index 00000000000..a918981dcd3 --- /dev/null +++ b/vespa-feed-client-cli/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +install_java_artifact(vespa-feed-client-cli) + +vespa_install_script(src/main/sh/vespa-feed-client.sh vespa-feed-client bin) diff --git a/vespa-feed-client-cli/OWNERS b/vespa-feed-client-cli/OWNERS new file mode 100644 index 00000000000..606d074d8a8 --- /dev/null +++ b/vespa-feed-client-cli/OWNERS @@ -0,0 +1,2 @@ +bjorncs +jonmv diff --git a/vespa-feed-client-cli/pom.xml b/vespa-feed-client-cli/pom.xml new file mode 100644 index 00000000000..62ff5c149ec --- /dev/null +++ b/vespa-feed-client-cli/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0"?> +<!-- Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>7-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + <artifactId>vespa-feed-client-cli</artifactId> + <packaging>jar</packaging> + <version>7-SNAPSHOT</version> + + <properties> + <maven.javadoc.skip>true</maven.javadoc.skip> + <!-- Used by internal properties that are still using JDK8--> + <maven.compiler.release>8</maven.compiler.release> + </properties> + + <dependencies> + <!-- compile scope --> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vespa-feed-client</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>commons-cli</groupId> + <artifactId>commons-cli</artifactId> + <scope>compile</scope> + </dependency> + + <!-- test scope --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <jdkToolchain> + <version>${java.version}</version> + </jdkToolchain> + <source>${java.version}</source> + <target>${java.version}</target> + <showDeprecation>true</showDeprecation> + <compilerArgs> + <arg>-Xlint:all</arg> + <arg>-Xlint:-serial</arg> + <arg>-Werror</arg> + </compilerArgs> + </configuration> + </plugin> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <archive> + <manifest> + <mainClass>ai.vespa.feed.client.CliClient</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/CliArguments.java b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/CliArguments.java index 63b438134d6..33c3d0e7894 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/CliArguments.java +++ b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/CliArguments.java @@ -16,6 +16,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; @@ -28,16 +31,18 @@ class CliArguments { private static final Options optionsDefinition = createOptions(); - private static final String HELP_OPTION = "help"; - private static final String VERSION_OPTION = "version"; + private static final String BENCHMARK_OPTION = "benchmark"; + private static final String CA_CERTIFICATES_OPTION = "ca-certificates"; + private static final String CERTIFICATE_OPTION = "certificate"; + private static final String CONNECTIONS_OPTION = "connections"; + private static final String DISABLE_SSL_HOSTNAME_VERIFICATION_OPTION = "disable-ssl-hostname-verification"; private static final String ENDPOINT_OPTION = "endpoint"; private static final String FILE_OPTION = "file"; - private static final String CONNECTIONS_OPTION = "connections"; + private static final String HEADER_OPTION = "header"; + private static final String HELP_OPTION = "help"; private static final String MAX_STREAMS_PER_CONNECTION = "max-streams-per-connection"; - private static final String CERTIFICATE_OPTION = "certificate"; private static final String PRIVATE_KEY_OPTION = "private-key"; - private static final String CA_CERTIFICATES_OPTION = "ca-certificates"; - private static final String DISABLE_SSL_HOSTNAME_VERIFICATION_OPTION = "disable-ssl-hostname-verification"; + private static final String VERSION_OPTION = "version"; private final CommandLine arguments; @@ -89,8 +94,28 @@ class CliArguments { .orElseThrow(() -> new CliArgumentsException("Feed file must be specified")); } + Map<String, String> headers() throws CliArgumentsException { + String[] rawArguments = arguments.getOptionValues(HEADER_OPTION); + if (rawArguments == null) return Collections.emptyMap(); + Map<String, String> headers = new HashMap<>(); + for (String rawArgument : rawArguments) { + if (rawArgument.startsWith("\"") || rawArgument.startsWith("'")) { + rawArgument = rawArgument.substring(1); + } + if (rawArgument.endsWith("\"") || rawArgument.endsWith("'")) { + rawArgument = rawArgument.substring(0, rawArgument.length() - 1); + } + int colonIndex = rawArgument.indexOf(':'); + if (colonIndex == -1) throw new CliArgumentsException("Invalid header: '" + rawArgument + "'"); + headers.put(rawArgument.substring(0, colonIndex), rawArgument.substring(colonIndex + 1).trim()); + } + return Collections.unmodifiableMap(headers); + } + boolean sslHostnameVerificationDisabled() { return has(DISABLE_SSL_HOSTNAME_VERIFICATION_OPTION); } + boolean benchmarkModeEnabled() { return has(BENCHMARK_OPTION); } + private OptionalInt intValue(String option) throws CliArgumentsException { try { Number number = (Number) arguments.getParsedOptionValue(option); @@ -113,6 +138,7 @@ class CliArguments { private boolean has(String option) { return arguments.hasOption(option); } private static Options createOptions() { + // TODO Add description to each option return new Options() .addOption(Option.builder() .longOpt(HELP_OPTION) @@ -126,6 +152,10 @@ class CliArguments { .type(URL.class) .build()) .addOption(Option.builder() + .longOpt(HEADER_OPTION) + .hasArgs() + .build()) + .addOption(Option.builder() .longOpt(FILE_OPTION) .type(File.class) .hasArg() @@ -162,6 +192,9 @@ class CliArguments { .build()) .addOption(Option.builder() .longOpt(DISABLE_SSL_HOSTNAME_VERIFICATION_OPTION) + .build()) + .addOption(Option.builder() + .longOpt(BENCHMARK_OPTION) .build()); } diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/CliClient.java b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/CliClient.java index 83a0c650318..27ecada98c3 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/CliClient.java +++ b/vespa-feed-client-cli/src/main/java/ai/vespa/feed/client/CliClient.java @@ -4,6 +4,7 @@ package ai.vespa.feed.client; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; import java.nio.file.Path; import java.util.Properties; @@ -13,25 +14,27 @@ import java.util.Properties; * * @author bjorncs */ -class CliClient { +public class CliClient { private final PrintStream systemOut; private final PrintStream systemError; + private final InputStream systemIn; private final Properties systemProperties; - CliClient(PrintStream systemOut, PrintStream systemError, Properties systemProperties) { + private CliClient(PrintStream systemOut, PrintStream systemError, InputStream systemIn, Properties systemProperties) { this.systemOut = systemOut; this.systemError = systemError; + this.systemIn = systemIn; this.systemProperties = systemProperties; } public static void main(String[] args) { - CliClient client = new CliClient(System.out, System.err, System.getProperties()); + CliClient client = new CliClient(System.out, System.err, System.in, System.getProperties()); int exitCode = client.run(args); System.exit(exitCode); } - int run(String[] rawArgs) { + private int run(String[] rawArgs) { try { CliArguments cliArgs = CliArguments.fromRawArgs(rawArgs); if (cliArgs.helpSpecified()) { @@ -68,6 +71,7 @@ class CliClient { } builder.setSslContext(sslContextBuilder.build()); } + cliArgs.headers().forEach(builder::addRequestHeader); return builder.build(); } diff --git a/vespa-feed-client-cli/src/main/sh/vespa-feed-client.sh b/vespa-feed-client-cli/src/main/sh/vespa-feed-client.sh new file mode 100755 index 00000000000..2a166dd40bb --- /dev/null +++ b/vespa-feed-client-cli/src/main/sh/vespa-feed-client.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# BEGIN environment bootstrap section +# Do not edit between here and END as this section should stay identical in all scripts + +findpath () { + myname=${0} + mypath=${myname%/*} + myname=${myname##*/} + empty_if_start_slash=${mypath%%/*} + if [ "${empty_if_start_slash}" ]; then + mypath=$(pwd)/${mypath} + fi + if [ "$mypath" ] && [ -d "$mypath" ]; then + return + fi + mypath=$(pwd) + if [ -f "${mypath}/${myname}" ]; then + return + fi + echo "FATAL: Could not figure out the path where $myname lives from $0" + exit 1 +} + +COMMON_ENV=libexec/vespa/common-env.sh + +source_common_env () { + if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then + export VESPA_HOME + common_env=$VESPA_HOME/$COMMON_ENV + if [ -f "$common_env" ]; then + . $common_env + return + fi + fi + return 1 +} + +findroot () { + source_common_env && return + if [ "$VESPA_HOME" ]; then + echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" + exit 1 + fi + if [ "$ROOT" ] && [ -d "$ROOT" ]; then + VESPA_HOME="$ROOT" + source_common_env && return + fi + findpath + while [ "$mypath" ]; do + VESPA_HOME=${mypath} + source_common_env && return + mypath=${mypath%/*} + done + echo "FATAL: missing VESPA_HOME environment variable" + echo "Could not locate $COMMON_ENV anywhere" + exit 1 +} + +findhost () { + if [ "${VESPA_HOSTNAME}" = "" ]; then + VESPA_HOSTNAME=$(vespa-detect-hostname || hostname -f || hostname || echo "localhost") || exit 1 + fi + validate="${VESPA_HOME}/bin/vespa-validate-hostname" + if [ -f "$validate" ]; then + "$validate" "${VESPA_HOSTNAME}" || exit 1 + fi + export VESPA_HOSTNAME +} + +findroot +findhost + +# END environment bootstrap section + +export MALLOC_ARENA_MAX=1 #Does not need fast allocation +exec java \ +-Djava.library.path=${VESPA_HOME}/libexec64/native:${VESPA_HOME}/lib64 \ +-Djava.awt.headless=true \ +-Xms128m -Xmx2048m $(getJavaOptionsIPV46) \ +-cp ${VESPA_HOME}/lib/jars/vespa-feed-client-cli.jar ai.vespa.feed.client.CliClient "$@" diff --git a/vespa-feed-client/src/test/java/ai/vespa/feed/client/CliArgumentsTest.java b/vespa-feed-client-cli/src/test/java/ai/vespa/feed/client/CliArgumentsTest.java index b8dda66bf96..33ee31ff0dc 100644 --- a/vespa-feed-client/src/test/java/ai/vespa/feed/client/CliArgumentsTest.java +++ b/vespa-feed-client-cli/src/test/java/ai/vespa/feed/client/CliArgumentsTest.java @@ -20,7 +20,8 @@ class CliArgumentsTest { CliArguments args = CliArguments.fromRawArgs(new String[]{ "--endpoint=https://vespa.ai:4443/", "--file=feed.json", "--connections=10", "--max-streams-per-connection=128", "--certificate=cert.pem", "--private-key=key.pem", - "--ca-certificates=ca-certs.pem", "--disable-ssl-hostname-verification"}); + "--ca-certificates=ca-certs.pem", "--disable-ssl-hostname-verification", + "--header=\"My-Header: my-value\"", "--header", "Another-Header: another-value", "--benchmark"}); assertEquals(URI.create("https://vespa.ai:4443/"), args.endpoint()); assertEquals(Paths.get("feed.json"), args.inputFile()); assertEquals(10, args.connections().getAsInt()); @@ -30,6 +31,11 @@ class CliArgumentsTest { assertEquals(Paths.get("ca-certs.pem"), args.caCertificates().get()); assertTrue(args.sslHostnameVerificationDisabled()); assertFalse(args.helpSpecified()); + assertFalse(args.versionSpecified()); + assertEquals(2, args.headers().size()); + assertEquals("my-value", args.headers().get("My-Header")); + assertEquals("another-value", args.headers().get("Another-Header")); + assertTrue(args.benchmarkModeEnabled()); } @Test diff --git a/vespa-feed-client/src/test/resources/help.txt b/vespa-feed-client-cli/src/test/resources/help.txt index d0017003a11..9ad7642d4ec 100644 --- a/vespa-feed-client/src/test/resources/help.txt +++ b/vespa-feed-client-cli/src/test/resources/help.txt @@ -1,11 +1,13 @@ usage: vespa-feed-client <options> Vespa feed client + --benchmark --ca-certificates <arg> --certificate <arg> --connections <arg> --disable-ssl-hostname-verification --endpoint <arg> --file <arg> + --header <arg> --help --max-streams-per-connection <arg> --private-key <arg> diff --git a/vespa-feed-client/CMakeLists.txt b/vespa-feed-client/CMakeLists.txt index ee6dfdeff05..7e6346c81cb 100644 --- a/vespa-feed-client/CMakeLists.txt +++ b/vespa-feed-client/CMakeLists.txt @@ -1,2 +1 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -# TODO Install fat jar
\ No newline at end of file diff --git a/vespa-feed-client/pom.xml b/vespa-feed-client/pom.xml index 4a44f1c0240..7759e9d2308 100644 --- a/vespa-feed-client/pom.xml +++ b/vespa-feed-client/pom.xml @@ -18,37 +18,18 @@ </properties> <dependencies> - <!-- provided --> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - <scope>provided</scope> - </dependency> + <!-- compile scope --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> - <scope>provided</scope> + <scope>compile</scope> </dependency> - - <!-- compile scope --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <scope>compile</scope> - <exclusions> - <exclusion> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </exclusion> - </exclusions> </dependency> <dependency> - <groupId>commons-cli</groupId> - <artifactId>commons-cli</artifactId> - <scope>compile</scope> - </dependency> - - <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <scope>compile</scope> diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java index 95a49abcc25..3cd3f3cb4ca 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/FeedClientBuilder.java @@ -3,7 +3,10 @@ package ai.vespa.feed.client; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.io.UncheckedIOException; import java.net.URI; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -27,6 +30,9 @@ public class FeedClientBuilder { int maxConnections = 4; int maxStreamsPerConnection = 1024; FeedClient.RetryStrategy retryStrategy = defaultRetryStrategy; + Path certificate; + Path privateKey; + Path caCertificates; public static FeedClientBuilder create(URI endpoint) { return new FeedClientBuilder(endpoint); } @@ -63,6 +69,9 @@ public class FeedClientBuilder { } public FeedClientBuilder setSslContext(SSLContext context) { + if (certificate != null || caCertificates != null || privateKey != null) { + throw new IllegalArgumentException("Cannot set both SSLContext and certificate / CA certificates"); + } this.sslContext = requireNonNull(context); return this; } @@ -86,8 +95,25 @@ public class FeedClientBuilder { return this; } + public FeedClientBuilder setCertificate(Path certificatePemFile, Path privateKeyPemFile) { + if (sslContext != null) throw new IllegalArgumentException("Cannot set both SSLContext and certificate"); + this.certificate = certificatePemFile; + this.privateKey = privateKeyPemFile; + return this; + } + + public FeedClientBuilder setCaCertificates(Path caCertificatesFile) { + if (sslContext != null) throw new IllegalArgumentException("Cannot set both SSLContext and CA certificate"); + this.caCertificates = caCertificatesFile; + return this; + } + public FeedClient build() { - return new HttpFeedClient(this); + try { + return new HttpFeedClient(this); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } } diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpFeedClient.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpFeedClient.java index fc1637fe17f..49f79c07d37 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpFeedClient.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpFeedClient.java @@ -5,19 +5,17 @@ import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; -import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; -import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.async.H2AsyncClientBuilder; import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.net.URIBuilder; -import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; +import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; @@ -47,7 +45,7 @@ class HttpFeedClient implements FeedClient { private final CloseableHttpAsyncClient httpClient; private final AtomicBoolean closed = new AtomicBoolean(); - HttpFeedClient(FeedClientBuilder builder) { + HttpFeedClient(FeedClientBuilder builder) throws IOException { this.endpoint = builder.endpoint; this.requestHeaders = new HashMap<>(builder.requestHeaders); @@ -56,45 +54,47 @@ class HttpFeedClient implements FeedClient { this.httpClient.start(); } - private static CloseableHttpAsyncClient createHttpClient(FeedClientBuilder builder, HttpRequestStrategy retryStrategy) { - HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create() - .setUserAgent(String.format("vespa-feed-client/%s", Vespa.VERSION)) - .setDefaultHeaders(Collections.singletonList(new BasicHeader("Vespa-Client-Version", Vespa.VERSION))) - .disableCookieManagement() - .disableRedirectHandling() - .disableConnectionState() - .setRetryStrategy(retryStrategy) - .setIOReactorConfig(IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(10)) - .build()) - .setDefaultRequestConfig( - RequestConfig.custom() - .setConnectTimeout(Timeout.ofSeconds(10)) - .setConnectionRequestTimeout(Timeout.DISABLED) - .setResponseTimeout(Timeout.ofMinutes(5)) - .build()) - .setH2Config(H2Config.custom() - .setMaxConcurrentStreams(builder.maxStreamsPerConnection) - .setCompressionEnabled(true) - .setPushEnabled(false) - .build()); - - int maxConnections = builder.maxConnections; - PoolingAsyncClientConnectionManagerBuilder connectionManagerBuilder = PoolingAsyncClientConnectionManagerBuilder.create() - .setConnectionTimeToLive(TimeValue.ofMinutes(10)) - .setMaxConnTotal(maxConnections) - .setMaxConnPerRoute(maxConnections) - .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX); - if (builder.sslContext != null) { - ClientTlsStrategyBuilder tlsStrategyBuilder = ClientTlsStrategyBuilder.create() - .setSslContext(builder.sslContext); - if (builder.hostnameVerifier != null) { - tlsStrategyBuilder.setHostnameVerifier(builder.hostnameVerifier); - } - connectionManagerBuilder.setTlsStrategy(tlsStrategyBuilder.build()); + private static CloseableHttpAsyncClient createHttpClient(FeedClientBuilder builder, HttpRequestStrategy retryStrategy) throws IOException { + H2AsyncClientBuilder httpClientBuilder = H2AsyncClientBuilder.create() + .setUserAgent(String.format("vespa-feed-client/%s", Vespa.VERSION)) + .setDefaultHeaders(Collections.singletonList(new BasicHeader("Vespa-Client-Version", Vespa.VERSION))) + .disableCookieManagement() + .disableRedirectHandling() + .setRetryStrategy(retryStrategy) + .setIOReactorConfig(IOReactorConfig.custom() + .setSoTimeout(Timeout.ofSeconds(10)) + .build()) + .setDefaultRequestConfig( + RequestConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(10)) + .setConnectionRequestTimeout(Timeout.DISABLED) + .setResponseTimeout(Timeout.ofMinutes(5)) + .build()) + .setH2Config(H2Config.initial() + .setMaxConcurrentStreams(builder.maxStreamsPerConnection) + .setCompressionEnabled(true) + .setPushEnabled(false) + .build()); + + ClientTlsStrategyBuilder tlsStrategyBuilder = ClientTlsStrategyBuilder.create() + .setSslContext(constructSslContext(builder)); + if (builder.hostnameVerifier != null) { + tlsStrategyBuilder.setHostnameVerifier(builder.hostnameVerifier); + } + return httpClientBuilder.setTlsStrategy(tlsStrategyBuilder.build()) + .build(); + } + + private static SSLContext constructSslContext(FeedClientBuilder builder) throws IOException { + if (builder.sslContext != null) return builder.sslContext; + SslContextBuilder sslContextBuilder = new SslContextBuilder(); + if (builder.certificate != null && builder.privateKey != null) { + sslContextBuilder.withCertificateAndKey(builder.certificate, builder.privateKey); + } + if (builder.caCertificates != null) { + sslContextBuilder.withCaCertificates(builder.caCertificates); } - httpClientBuilder.setConnectionManager(connectionManagerBuilder.build()); - return httpClientBuilder.build(); + return sslContextBuilder.build(); } @Override diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java index 652fea9b332..734c46a2819 100644 --- a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java +++ b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java @@ -75,6 +75,11 @@ public abstract class Maintainer implements Runnable { } } + /** Returns whether this is being shut down */ + public final boolean shuttingDown() { + return shutDown.get(); + } + @Override public final String toString() { return name(); } diff --git a/vespajlib/src/main/java/com/yahoo/lang/MutableBoolean.java b/vespajlib/src/main/java/com/yahoo/lang/MutableBoolean.java index b009ffdd2b6..17501b17bd0 100644 --- a/vespajlib/src/main/java/com/yahoo/lang/MutableBoolean.java +++ b/vespajlib/src/main/java/com/yahoo/lang/MutableBoolean.java @@ -18,6 +18,10 @@ public class MutableBoolean { public void set(boolean value) { this.value = value; } + public void andSet(boolean value) { this.value &= value; } + + public void orSet(boolean value) { this.value |= value; } + @Override public String toString() { return Boolean.toString(value); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java b/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java index 9eb9cb06666..461e73e3611 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/serialization/JsonFormat.java @@ -216,7 +216,7 @@ public class JsonFormat { return result; } - private static double[] decodeHexString(String input, TensorType.Value valueType) { + public static double[] decodeHexString(String input, TensorType.Value valueType) { switch(valueType) { case INT8: return decodeHexStringAsBytes(input); diff --git a/zookeeper-server/zookeeper-server-3.6.2/CMakeLists.txt b/zookeeper-server/zookeeper-server-3.6.2/CMakeLists.txt index c85a65e8e78..1167067ec73 100644 --- a/zookeeper-server/zookeeper-server-3.6.2/CMakeLists.txt +++ b/zookeeper-server/zookeeper-server-3.6.2/CMakeLists.txt @@ -1,4 +1,4 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. install_fat_java_artifact(zookeeper-server-3.6.2) # Needs to be included when this is the wanted default version (and symlinks for other versions need to be removed) -install_symlink(lib/jars/zookeeper-server-3.6.2-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) +#install_symlink(lib/jars/zookeeper-server-3.6.2-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) diff --git a/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt b/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt index ddccb1f0dbc..af216e0e8c2 100644 --- a/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt +++ b/zookeeper-server/zookeeper-server-3.6.3/CMakeLists.txt @@ -1,4 +1,4 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. install_fat_java_artifact(zookeeper-server-3.6.3) # Needs to be included when this is the wanted default version (and symlinks for other versions need to be removed) -#install_symlink(lib/jars/zookeeper-server-3.6.3-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) +install_symlink(lib/jars/zookeeper-server-3.6.3-jar-with-dependencies.jar lib/jars/zookeeper-server-jar-with-dependencies.jar) diff --git a/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java b/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java index 113669b2e76..c4062c64dd4 100644 --- a/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java +++ b/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java @@ -30,7 +30,7 @@ class VespaQuorumPeer extends QuorumPeerMain implements QuorumPeer { @Override public void shutdown(Duration timeout) { if (quorumPeer != null) { - log.log(Level.INFO, "Shutting down ZooKeeper server"); + log.log(Level.FINE, "Shutting down ZooKeeper server"); try { quorumPeer.shutdown(); quorumPeer.join(timeout.toMillis()); // Wait for shutdown to complete @@ -43,7 +43,7 @@ class VespaQuorumPeer extends QuorumPeerMain implements QuorumPeer { // server with the new config, this will fail until the old server is deconstructed. If the old server // fails to deconstruct/shut down, the new one will never start and if that happens forcing a restart is // the better option. - Process.logAndDie("Failed to shut down ZooKeeper properly, forcing shutdown", e); + Process.logAndDie("Failed to shut down ZooKeeper server properly, forcing shutdown", e); } } } diff --git a/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java b/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java index d92527fb5fd..b41b80d6f93 100644 --- a/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java +++ b/zookeeper-server/zookeeper-server-3.6.3/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java @@ -38,8 +38,7 @@ public class VespaZooKeeperAdminImpl implements VespaZooKeeperAdmin { if (zooKeeperAdmin != null) { try { zooKeeperAdmin.close(); - } catch (InterruptedException e) { - } + } catch (InterruptedException e) { /* ignore */} } } } diff --git a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Configurator.java b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Configurator.java index 9705c8c379e..863374f40f6 100644 --- a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Configurator.java +++ b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Configurator.java @@ -1,5 +1,4 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - package com.yahoo.vespa.zookeeper; import com.yahoo.cloud.config.ZookeeperServerConfig; @@ -75,8 +74,8 @@ public class Configurator { sb.append("dataDir=").append(getDefaults().underVespaHome(config.dataDir())).append("\n"); sb.append("autopurge.purgeInterval=").append(config.autopurge().purgeInterval()).append("\n"); sb.append("autopurge.snapRetainCount=").append(config.autopurge().snapRetainCount()).append("\n"); - // See http://zookeeper.apache.org/doc/r3.5.5/zookeeperAdmin.html#sc_zkCommands - // Includes all available commands in 3.5, except 'wchc' and 'wchp' + // See http://zookeeper.apache.org/doc/r3.6.3/zookeeperAdmin.html#sc_zkCommands + // Includes all available commands in 3.6, except 'wchc' and 'wchp' sb.append("4lw.commands.whitelist=conf,cons,crst,dirs,dump,envi,mntr,ruok,srst,srvr,stat,wchs").append("\n"); sb.append("admin.enableServer=false").append("\n"); // Use custom connection factory for TLS on client port - see class' Javadoc for rationale @@ -107,7 +106,7 @@ public class Configurator { } } if (!found) { - throw new RuntimeException("No id in zookeeper server list that corresponds to my id(" + myid + ")"); + throw new RuntimeException("No id in zookeeper server list that corresponds to my id (" + myid + ")"); } } @@ -125,7 +124,7 @@ public class Configurator { // actually make the server an observer, but prevent it from forming an ensemble independently of the // existing cluster. // - // See https://zookeeper.apache.org/doc/r3.6.2/zookeeperReconfig.html#sc_reconfig_modifying + // See https://zookeeper.apache.org/doc/r3.6.3/zookeeperReconfig.html#sc_reconfig_modifying sb.append(":") .append("observer"); } diff --git a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ReconfigException.java b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ReconfigException.java index c4bb09eeedf..765bff474a8 100644 --- a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ReconfigException.java +++ b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ReconfigException.java @@ -2,8 +2,8 @@ package com.yahoo.vespa.zookeeper; /** - * Exception used to wrap zookeeper exception when reconfiguration fails in a - * class that can be used without depending on ZooKeeper. + * Exception used to wrap zookeeper exception when reconfiguration fails, done to be able + * to use a class that does not depend on ZooKeeper. * * @author hmusum */ diff --git a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Reconfigurer.java b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Reconfigurer.java index 9c2a543d2ef..f0318ddfcad 100644 --- a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Reconfigurer.java +++ b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/Reconfigurer.java @@ -20,8 +20,8 @@ import java.util.logging.Logger; import java.util.stream.Collectors; /** - * Starts zookeeper server and supports reconfiguring zookeeper cluster. Created as a component - * without any config injected, to make sure that it is not recreated when config changes. + * Starts zookeeper server and supports reconfiguring zookeeper cluster. Keep this as a component + * without injected config, to make sure that it is not recreated when config changes. * * @author hmusum */ @@ -48,7 +48,6 @@ public class Reconfigurer extends AbstractComponent { Reconfigurer(VespaZooKeeperAdmin vespaZooKeeperAdmin, Sleeper sleeper) { this.vespaZooKeeperAdmin = Objects.requireNonNull(vespaZooKeeperAdmin); this.sleeper = Objects.requireNonNull(sleeper); - log.log(Level.FINE, "Created ZooKeeperReconfigurer"); } void startOrReconfigure(ZookeeperServerConfig newConfig, VespaZooKeeperServer server, @@ -155,7 +154,7 @@ public class Reconfigurer extends AbstractComponent { } private static List<String> servers(ZookeeperServerConfig config) { - // See https://zookeeper.apache.org/doc/r3.5.8/zookeeperReconfig.html#sc_reconfig_clientport for format + // See https://zookeeper.apache.org/doc/r3.6.3/zookeeperReconfig.html#sc_reconfig_clientport for format return config.server().stream() .map(server -> server.id() + "=" + server.hostname() + ":" + server.quorumPort() + ":" + server.electionPort() + ";" + config.clientPort()) diff --git a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ZooKeeperRunner.java b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ZooKeeperRunner.java index 8c748250503..db8db1ac4b7 100644 --- a/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ZooKeeperRunner.java +++ b/zookeeper-server/zookeeper-server-common/src/main/java/com/yahoo/vespa/zookeeper/ZooKeeperRunner.java @@ -39,7 +39,7 @@ public class ZooKeeperRunner implements Runnable { this.zookeeperServerConfig = zookeeperServerConfig; this.server = server; new Configurator(zookeeperServerConfig).writeConfigToDisk(); - executorService = Executors.newSingleThreadExecutor(new DaemonThreadFactory("zookeeper server")); + executorService = Executors.newSingleThreadExecutor(new DaemonThreadFactory("zookeeper-server-")); executorService.submit(this); } @@ -66,14 +66,13 @@ public class ZooKeeperRunner implements Runnable { Instant end = now.plus(START_TIMEOUT); for (int attempt = 1; now.isBefore(end) && !executorService.isShutdown(); attempt++) { try { - log.log(Level.INFO, "Starting ZooKeeper server with config file " + path.toFile().getAbsolutePath() + + log.log(Level.INFO, "Starting ZooKeeper server with " + path.toFile().getAbsolutePath() + ". Trying to establish ZooKeeper quorum (members: " + - zookeeperServerHostnames(zookeeperServerConfig) + ", attempt: " + attempt + ")"); + zookeeperServerHostnames(zookeeperServerConfig) + ", attempt " + attempt + ")"); startServer(path); // Will block in a real implementation of VespaZooKeeperServer return; } catch (RuntimeException e) { - String messagePart = "Starting " + serverDescription() + " failed on attempt " + - attempt; + String messagePart = "Starting " + serverDescription() + " failed on attempt " + attempt; if (server.reconfigurable()) { Duration delay = backoff.delay(attempt); log.log(Level.WARNING, messagePart + ". Retrying in " + delay + ", time left " + |