diff options
author | Henrik <henrik.hoiness@online.no> | 2018-08-06 15:29:18 +0200 |
---|---|---|
committer | Henrik <henrik.hoiness@online.no> | 2018-08-06 15:29:18 +0200 |
commit | a9ce32c11684e612d5bf68686ee21414677d01ee (patch) | |
tree | fe15f2cba213c33cc94e2aaba1cb866ebdbbd641 | |
parent | c31c1eee87a00774f34e985b70568af0eea0c82b (diff) | |
parent | 2764585ef3ab81e74af7aa7ca2709c6c4d8046a6 (diff) |
Solve conflicting files
60 files changed, 856 insertions, 629 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c557179750..3961fd2a4ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ include(build_settings.cmake) # Enable CTest unit testing enable_testing() +vespa_install_data(valgrind-suppressions.txt etc/vespa) + # Include vespa config definitions in every target include_directories(BEFORE ${CMAKE_BINARY_DIR}/configdefinitions/src) diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java index 0abbb5a64f5..950d2df9532 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java @@ -5,7 +5,6 @@ import com.google.inject.Inject; import com.yahoo.config.provision.Zone; import com.yahoo.net.HostName; import com.yahoo.vespa.athenz.api.AthenzService; -import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.IdentityType; import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; @@ -17,7 +16,6 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Allocation; -import java.net.URI; import java.security.PrivateKey; import java.time.Instant; import java.util.HashSet; @@ -49,57 +47,46 @@ public class IdentityDocumentGenerator { } public SignedIdentityDocument generateSignedIdentityDocument(String hostname, IdentityType identityType) { - Node node = nodeRepository.getNode(hostname).orElseThrow(() -> new RuntimeException("Unable to find node " + hostname)); try { - IdentityDocument identityDocument = generateIdDocument(node, identityType); + Node node = nodeRepository.getNode(hostname).orElseThrow(() -> new RuntimeException("Unable to find node " + hostname)); + Allocation allocation = node.allocation().orElseThrow(() -> new RuntimeException("No allocation for node " + node.hostname())); + VespaUniqueInstanceId providerUniqueId = new VespaUniqueInstanceId( + allocation.membership().index(), + allocation.membership().cluster().id().value(), + allocation.owner().instance().value(), + allocation.owner().application().value(), + allocation.owner().tenant().value(), + zone.region().value(), + zone.environment().value(), + identityType); + + Set<String> ips = new HashSet<>(node.ipAddresses()); PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); AthenzService providerService = new AthenzService(zoneConfig.domain(), zoneConfig.serviceName()); + String configServerHostname = HostName.getLocalhost(); + Instant createdAt = Instant.now(); String signature = signer.generateSignature( - identityDocument.providerUniqueId(), providerService, identityDocument.configServerHostname(), - identityDocument.instanceHostname(), identityDocument.createdAt(), identityDocument.ipAddresses(), identityType, privateKey); + providerUniqueId, providerService, configServerHostname, + node.hostname(), createdAt, ips, identityType, privateKey); return new SignedIdentityDocument( - identityDocument, signature, SignedIdentityDocument.DEFAULT_KEY_VERSION, - identityDocument.providerUniqueId(), - toZoneDnsSuffix(zone, zoneConfig.certDnsSuffix()), + providerUniqueId, providerService, - URI.create(zoneConfig.ztsUrl()), SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION, - identityDocument.configServerHostname(), - identityDocument.instanceHostname(), - identityDocument.createdAt(), - identityDocument.ipAddresses(), + configServerHostname, + node.hostname(), + createdAt, + ips, identityType); } catch (Exception e) { throw new RuntimeException("Exception generating identity document: " + e.getMessage(), e); } } - private IdentityDocument generateIdDocument(Node node, IdentityType identityType) { - Allocation allocation = node.allocation().orElseThrow(() -> new RuntimeException("No allocation for node " + node.hostname())); - VespaUniqueInstanceId providerUniqueId = new VespaUniqueInstanceId( - allocation.membership().index(), - allocation.membership().cluster().id().value(), - allocation.owner().instance().value(), - allocation.owner().application().value(), - allocation.owner().tenant().value(), - zone.region().value(), - zone.environment().value(), - identityType); - - Set<String> ips = new HashSet<>(node.ipAddresses()); - return new IdentityDocument( - providerUniqueId, - HostName.getLocalhost(), - node.hostname(), - Instant.now(), - ips); - } - private static String toZoneDnsSuffix(Zone zone, String dnsSuffix) { return zone.environment().value() + "-" + zone.region().value() + "." + dnsSuffix; } diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java index a1839ec62a2..48e85f6047e 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGeneratorTest.java @@ -87,8 +87,6 @@ public class IdentityDocumentGeneratorTest { String environment = "dev"; String region = "us-north-1"; - String expectedZoneDnsSuffix = environment + "-" + region + "." + dnsSuffix; - assertEquals(expectedZoneDnsSuffix, signedIdentityDocument.dnsSuffix()); VespaUniqueInstanceId expectedProviderUniqueId = new VespaUniqueInstanceId(0, "default", "default", "application", "tenant", region, environment, IdentityType.TENANT); diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java index 04c4d4da51a..56777325231 100644 --- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java +++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidatorTest.java @@ -1,8 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.athenz.instanceproviderservice.instanceconfirmation; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableSet; import com.yahoo.config.model.api.ApplicationInfo; import com.yahoo.config.model.api.HostInfo; import com.yahoo.config.model.api.Model; @@ -10,18 +8,8 @@ import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.config.model.api.SuperModel; import com.yahoo.config.model.api.SuperModelProvider; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.IdentityDocumentEntity; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.VespaUniqueInstanceIdEntity; -import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils; import org.junit.Test; -import java.net.URI; -import java.security.PrivateKey; -import java.security.Signature; -import java.time.Instant; -import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -40,6 +28,7 @@ import static org.mockito.Mockito.when; /** * @author valerijf + * @author bjorncs */ public class InstanceValidatorTest { @@ -93,44 +82,6 @@ public class InstanceValidatorTest { assertTrue(instanceValidator.isSameIdentityAsInServicesXml(applicationId, domain, service)); } - private static InstanceConfirmation createInstanceConfirmation(PrivateKey privateKey, ApplicationId applicationId, - String domain, String service) { - IdentityDocumentEntity identityDocument = new IdentityDocumentEntity( - new VespaUniqueInstanceIdEntity(applicationId.tenant().value(), applicationId.application().value(), - "environment", "region", applicationId.instance().value(), "cluster-id", 0), - "hostname", - "instance-hostname", - Instant.now(), - ImmutableSet.of("127.0.0.1", "::1")); - - try { - ObjectMapper mapper = Utils.getMapper(); - String encodedIdentityDocument = - Base64.getEncoder().encodeToString(mapper.writeValueAsString(identityDocument).getBytes()); - Signature sigGenerator = Signature.getInstance("SHA512withRSA"); - sigGenerator.initSign(privateKey); - sigGenerator.update(encodedIdentityDocument.getBytes()); - - return new InstanceConfirmation( - "provider", domain, service, - new SignedIdentityDocumentEntity(encodedIdentityDocument, - Base64.getEncoder().encodeToString(sigGenerator.sign()), - 0, - EntityBindingsMapper.toVespaUniqueInstanceId(identityDocument.providerUniqueId).asDottedString(), - "dnssuffix", - "service", - URI.create("http://localhost/zts"), - 1, - identityDocument.configServerHostname, - identityDocument.instanceHostname, - identityDocument.createdAt, - identityDocument.ipAddresses, - null)); // TODO Remove support for legacy representation without type - } catch (Exception e) { - throw new RuntimeException(e); - } - } - private SuperModelProvider mockSuperModelProvider(ApplicationInfo... appInfos) { SuperModel superModel = new SuperModel(Stream.of(appInfos) .collect(Collectors.groupingBy( diff --git a/container-search/src/main/java/com/yahoo/search/Query.java b/container-search/src/main/java/com/yahoo/search/Query.java index 4ec0b6e426c..cfb1c9a26be 100644 --- a/container-search/src/main/java/com/yahoo/search/Query.java +++ b/container-search/src/main/java/com/yahoo/search/Query.java @@ -193,6 +193,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable { public static final CompoundName GROUPING_SESSION_CACHE = new CompoundName("groupingSessionCache"); public static final CompoundName TIMEOUT = new CompoundName("timeout"); + private static QueryProfileType argumentType; static { argumentType = new QueryProfileType("native"); @@ -216,6 +217,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable { } public static QueryProfileType getArgumentType() { return argumentType; } + /** The aliases of query properties */ private static Map<String,CompoundName> propertyAliases; static { diff --git a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java index d9fa6d8097f..c9e723db6d0 100644 --- a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java +++ b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java @@ -283,13 +283,16 @@ public class SearchHandler extends LoggingRequestHandler { private HttpSearchResponse handleBody(HttpRequest request){ - // Find query profile - String queryProfileName = request.getProperty("queryProfile"); + + Map<String, String> requestMap = requestMapFromRequest(request); + + // Get query profile + String queryProfileName = requestMap.getOrDefault("queryProfile", null); CompiledQueryProfile queryProfile = queryProfileRegistry.findQueryProfile(queryProfileName); - boolean benchmarkOutput = VespaHeaders.benchmarkOutput(request); - Query query = queryFromRequest(request, queryProfile); + Query query = new Query(request, requestMap, queryProfile); + boolean benchmarkOutput = VespaHeaders.benchmarkOutput(request); boolean benchmarkCoverage = VespaHeaders.benchmarkCoverage(benchmarkOutput, request.getJDiscRequest().headers()); // Find and execute search chain if we have a valid query @@ -558,7 +561,8 @@ public class SearchHandler extends LoggingRequestHandler { return searchChainRegistry; } - private Query queryFromRequest(HttpRequest request, CompiledQueryProfile queryProfile){ + private Map<String, String> requestMapFromRequest(HttpRequest request) { + if (request.getMethod() == com.yahoo.jdisc.http.HttpRequest.Method.POST && JSON_CONTENT_TYPE.equals(request.getHeader(com.yahoo.jdisc.http.HttpHeaders.Names.CONTENT_TYPE))) { Inspector inspector; @@ -588,11 +592,10 @@ public class SearchHandler extends LoggingRequestHandler { requestMap.remove("select.grouping"); } - return new Query(request, requestMap, queryProfile); - + return requestMap; } else { - return new Query(request, queryProfile); + return request.propertyMap(); } } diff --git a/dist/vespa.spec b/dist/vespa.spec index 0e3cabcea80..8af24c019b1 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -56,7 +56,7 @@ BuildRequires: gtest-devel BuildRequires: gmock-devel %endif %if 0%{?fc29} -BuildRequires: llvm4.0-devel >= 4.0 +BuildRequires: llvm3.9-devel >= 3.9.1 BuildRequires: boost-devel >= 1.66 BuildRequires: gtest-devel BuildRequires: gmock-devel @@ -130,10 +130,10 @@ Requires: llvm4.0-libs >= 4.0 %define _vespa_llvm_include_directory /usr/include/llvm4.0 %endif %if 0%{?fc29} -Requires: llvm4.0-libs >= 4.0 -%define _vespa_llvm_version 4.0 -%define _vespa_llvm_link_directory /usr/lib64/llvm4.0/lib -%define _vespa_llvm_include_directory /usr/include/llvm4.0 +Requires: llvm3.9-libs >= 3.9.1 +%define _vespa_llvm_version 3.9 +%define _vespa_llvm_link_directory /usr/lib64/llvm3.9/lib +%define _vespa_llvm_include_directory /usr/include/llvm3.9 %endif %define _extra_link_directory /opt/vespa-cppunit/lib%{?_vespa_llvm_link_directory:;%{_vespa_llvm_link_directory}}%{?_vespa_gtest_link_directory:;%{_vespa_gtest_link_directory}} %define _extra_include_directory /opt/vespa-cppunit/include%{?_vespa_llvm_include_directory:;%{_vespa_llvm_include_directory}}%{?_vespa_gtest_include_directory:;%{_vespa_gtest_include_directory}} diff --git a/document/src/tests/fieldpathupdatetestcase.cpp b/document/src/tests/fieldpathupdatetestcase.cpp index fb3ba3f7e40..f5db5831912 100644 --- a/document/src/tests/fieldpathupdatetestcase.cpp +++ b/document/src/tests/fieldpathupdatetestcase.cpp @@ -33,6 +33,7 @@ struct FieldPathUpdateTestCase : public CppUnit::TestFixture { void tearDown() override; void testWhereClause(); + void testBrokenWhereClause(); void testNoIterateMapValues(); void testRemoveField(); void testApplyRemoveEntireListField(); @@ -73,6 +74,7 @@ struct FieldPathUpdateTestCase : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(FieldPathUpdateTestCase); CPPUNIT_TEST(testWhereClause); + CPPUNIT_TEST(testBrokenWhereClause); CPPUNIT_TEST(testNoIterateMapValues); CPPUNIT_TEST(testRemoveField); CPPUNIT_TEST(testApplyRemoveEntireListField); @@ -356,6 +358,17 @@ FieldPathUpdateTestCase::testWhereClause() } void +FieldPathUpdateTestCase::testBrokenWhereClause() +{ + DocumentTypeRepo repo(getRepoConfig()); + Document::UP doc(createTestDocument(repo)); + std::string where = "l1s1.structmap.value.smap{$x} == \"dicaprio\""; + TestFieldPathUpdate update("l1s1.structmap.value.smap{$x}", where); + update.applyTo(*doc); + CPPUNIT_ASSERT_EQUAL(std::string(""), update._str); +} + +void FieldPathUpdateTestCase::testNoIterateMapValues() { DocumentTypeRepo repo(getRepoConfig()); diff --git a/document/src/vespa/document/update/fieldpathupdate.cpp b/document/src/vespa/document/update/fieldpathupdate.cpp index e7d5824b6e0..fa7a8b38aba 100644 --- a/document/src/vespa/document/update/fieldpathupdate.cpp +++ b/document/src/vespa/document/update/fieldpathupdate.cpp @@ -3,7 +3,9 @@ #include <vespa/document/datatype/datatype.h> #include <vespa/document/fieldvalue/document.h> #include <vespa/document/fieldvalue/iteratorhandler.h> +#include <vespa/document/select/constant.h> #include <vespa/document/select/parser.h> +#include <vespa/document/select/parsing_failed_exception.h> #include <vespa/document/util/serializableexceptions.h> #include <vespa/vespalib/objects/nbostream.h> #include <ostream> @@ -11,6 +13,7 @@ #include <vespa/log/log.h> LOG_SETUP(".document.update.fieldpathupdate"); +using document::select::ParsingFailedException; using vespalib::make_string; using vespalib::IllegalArgumentException; @@ -26,8 +29,13 @@ std::unique_ptr<select::Node> parseDocumentSelection(vespalib::stringref query, const DocumentTypeRepo& repo) { BucketIdFactory factory; - select::Parser parser(repo, factory); - return parser.parse(query); + try { + select::Parser parser(repo, factory); + return parser.parse(query); + } catch (const ParsingFailedException &e) { + LOG(warning, "Failed to parse selection for field path update: %s", e.getMessage().c_str()); + return std::make_unique<select::Constant>(false); + } } } // namespace diff --git a/functions.cmake b/functions.cmake index c0e07c1d362..f0dd5a31949 100644 --- a/functions.cmake +++ b/functions.cmake @@ -422,6 +422,14 @@ function(vespa_install_script) endif() endfunction() +function(vespa_install_data) + if(ARGC GREATER 2) + install(FILES ${ARGV0} RENAME ${ARGV1} PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ DESTINATION ${ARGV2}) + else() + install(FILES ${ARGV0} PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ DESTINATION ${ARGV1}) + endif() +endfunction() + function(vespa_workaround_gcc_bug_67055 SOURCE_FILE) if(CMAKE_COMPILER_IS_GNUCC) execute_process(COMMAND ${CMAKE_CPP_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) diff --git a/node-admin/src/main/application/services.xml b/node-admin/src/main/application/services.xml index 96fe82a5b94..284b356d2ca 100644 --- a/node-admin/src/main/application/services.xml +++ b/node-admin/src/main/application/services.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" ?> <!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> <services version="1.0" xmlns:preprocess="properties"> - <jdisc id="node-admin" jetty="true" version="1.0"> + <container id="node-admin" version="1.0"> <!-- Please update container test when changing this file --> <accesslog type="vespa" fileNamePattern="logs/vespa/node-admin/access.log.%Y%m%d%H%M%S" rotationScheme="date" symlinkName="access.log" /> <component id="docker-api" class="com.yahoo.vespa.hosted.dockerapi.DockerImpl" bundle="docker-api"/> @@ -13,5 +13,5 @@ </config> <preprocess:include file="variant.xml" required="false"/> - </jdisc> + </container> </services> diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java index 93243f8b8ed..9e94f6ed7e4 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java @@ -1,20 +1,14 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.component; -import com.google.common.base.Strings; -import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.utils.AthenzIdentities; import com.yahoo.vespa.hosted.node.admin.config.ConfigServerConfig; -import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions; import java.net.URI; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.Function; import static java.util.stream.Collectors.toMap; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java index 0c49e478d6a..3c44186f78d 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java @@ -21,8 +21,8 @@ public interface TaskContext { * to bob". */ void recordSystemModification(Logger logger, String message); - default void recordSystemModification(Logger logger, String messageFormat, String... args) { - recordSystemModification(logger, String.format(messageFormat, (Object[]) args)); + default void recordSystemModification(Logger logger, String messageFormat, Object... args) { + recordSystemModification(logger, String.format(messageFormat, args)); } /** @@ -35,8 +35,8 @@ public interface TaskContext { * Do not log a message that is also recorded with recordSystemModification. */ default void log(Logger logger, String message) {} - default void log(Logger logger, String messageFormat, String... args) { - log(logger, String.format(messageFormat, (Object[]) args)); + default void log(Logger logger, String messageFormat, Object... args) { + log(logger, String.format(messageFormat, args)); } /** diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java index 12ba777f018..aea44e728ad 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java @@ -110,41 +110,26 @@ public class ConfigServerApiImpl implements ConfigServerApi { private <T> T tryAllConfigServers(CreateRequest requestFactory, Class<T> wantedReturnType) { Exception lastException = null; for (URI configServer : configServers) { - final CloseableHttpResponse response; - try { - response = client.execute(requestFactory.createRequest(configServer)); - } catch (Exception e) { - // Failure to communicate with a config server is not abnormal, as they are - // upgraded at the same time as Docker hosts. - if (e.getMessage().indexOf("(Connection refused)") > 0) { - NODE_ADMIN_LOGGER.info("Connection refused to " + configServer + " (upgrading?), will try next"); - } else { - NODE_ADMIN_LOGGER.warning("Failed to communicate with " + configServer + ", will try next: " + e.getMessage()); - } - lastException = e; - continue; - } - - try { - Optional<HttpException> retryableException = HttpException.handleStatusCode( - response.getStatusLine().getStatusCode(), - "Config server " + configServer); - if (retryableException.isPresent()) { - lastException = retryableException.get(); - continue; - } + try (CloseableHttpResponse response = client.execute(requestFactory.createRequest(configServer))) { + HttpException.handleStatusCode( + response.getStatusLine().getStatusCode(), "Config server " + configServer); try { return mapper.readValue(response.getEntity().getContent(), wantedReturnType); } catch (IOException e) { - throw new RuntimeException("Response didn't contain nodes element, failed parsing?", e); + throw new RuntimeException("Failed parse response from config server", e); } - } finally { - try { - response.close(); - } catch (IOException e) { - NODE_ADMIN_LOGGER.warning("Ignoring exception from closing response", e); + } catch (HttpException e) { + if (!e.isRetryable()) throw e; + lastException = e; + } catch (Exception e) { + // Failure to communicate with a config server is not abnormal during upgrades + if (e.getMessage().contains("(Connection refused)")) { + NODE_ADMIN_LOGGER.info("Connection refused to " + configServer + " (upgrading?), will try next"); + } else { + NODE_ADMIN_LOGGER.warning("Failed to communicate with " + configServer + ", will try next: " + e.getMessage()); } + lastException = e; } } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java index d0f436d16b6..256fe38ec68 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.node.admin.configserver; import javax.ws.rs.core.Response; -import java.util.Optional; /** * @author hakonhall @@ -10,35 +9,34 @@ import java.util.Optional; @SuppressWarnings("serial") public class HttpException extends RuntimeException { - public static class NotFoundException extends HttpException { - - public NotFoundException(String message) { - super(Response.Status.NOT_FOUND, message); - } + private final boolean isRetryable; + private HttpException(int statusCode, String message, boolean isRetryable) { + super("HTTP status code " + statusCode + ": " + message); + this.isRetryable = isRetryable; } - public static class ForbiddenException extends HttpException { - - public ForbiddenException(String message) { - super(Response.Status.FORBIDDEN, message); - } + private HttpException(Response.Status status, String message, boolean isRetryable) { + super(status.toString() + " (" + status.getStatusCode() + "): " + message); + this.isRetryable = isRetryable; + } + public boolean isRetryable() { + return isRetryable; } /** - * Returns empty on success. - * Returns an exception if the error is retriable. - * Throws an exception on a non-retriable error, like 404 Not Found. + * Returns on success. + * @throws HttpException for all non-expected status codes. */ - static Optional<HttpException> handleStatusCode(int statusCode, String message) { + static void handleStatusCode(int statusCode, String message) { Response.Status status = Response.Status.fromStatusCode(statusCode); if (status == null) { - return Optional.of(new HttpException(statusCode, message)); + throw new HttpException(statusCode, message, true); } switch (status.getFamily()) { - case SUCCESSFUL: return Optional.empty(); + case SUCCESSFUL: return; case CLIENT_ERROR: switch (status) { case FORBIDDEN: @@ -48,20 +46,24 @@ public class HttpException extends RuntimeException { case CONFLICT: // A response body is assumed to be present, and // will later be interpreted as an error. - return Optional.empty(); + return; } - throw new HttpException(statusCode, message); + throw new HttpException(status, message, false); } // Other errors like server-side errors are assumed to be retryable. - return Optional.of(new HttpException(status, message)); + throw new HttpException(status, message, true); } - private HttpException(int statusCode, String message) { - super("HTTP status code " + statusCode + ": " + message); + public static class NotFoundException extends HttpException { + public NotFoundException(String message) { + super(Response.Status.NOT_FOUND, message, false); + } } - private HttpException(Response.Status status, String message) { - super(status.toString() + " (" + status.getStatusCode() + "): " + message); + public static class ForbiddenException extends HttpException { + public ForbiddenException(String message) { + super(Response.Status.FORBIDDEN, message, false); + } } } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java index ea92ca9b56f..383c025e2cb 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java @@ -89,6 +89,13 @@ public class StorageMaintainer { "129600", "--crit", "1", "--coredir", environment.pathInNodeUnderVespaHome("var/crash/processing").toString()); configs.add(annotatedCheck(node, coredumpSchedule)); + // athenz certificate check + Path athenzCertExpiryCheckPath = environment.pathInNodeUnderVespaHome("libexec64/yms/yms_check_athenz_certs"); + SecretAgentCheckConfig athenzCertExpirySchedule = new SecretAgentCheckConfig("athenz-certificate-expiry", 60, + athenzCertExpiryCheckPath, "--threshold", "20") + .withRunAsUser("root"); + configs.add(annotatedCheck(node, athenzCertExpirySchedule)); + if (node.getNodeType() != NodeType.config) { // vespa-health Path vespaHealthCheckPath = environment.pathInNodeUnderVespaHome("libexec/yms/yms_check_vespa_health"); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java new file mode 100644 index 00000000000..a815515ac83 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java @@ -0,0 +1,58 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.task.util.file; + +import com.yahoo.vespa.hosted.node.admin.component.TaskContext; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.OptionalInt; +import java.util.function.Supplier; +import java.util.logging.Logger; + +/** + * Class wrapping an integer stored on disk + * + * @author freva + */ +public class StoredInteger implements Supplier<OptionalInt> { + + private static final Logger logger = Logger.getLogger(StoredInteger.class.getName()); + + private final Path path; + private OptionalInt value; + private boolean hasBeenRead = false; + + public StoredInteger(Path path) { + this.path = path; + } + + @Override + public OptionalInt get() { + if (!hasBeenRead) { + try { + String value = new String(Files.readAllBytes(path)); + this.value = OptionalInt.of(Integer.valueOf(value)); + } catch (NoSuchFileException e) { + this.value = OptionalInt.empty(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read integer in " + path, e); + } + hasBeenRead = true; + } + return value; + } + + public void write(TaskContext taskContext, int value) { + try { + Files.write(path, Integer.toString(value).getBytes()); + this.value = OptionalInt.of(value); + this.hasBeenRead = true; + taskContext.log(logger, "Stored new integer in %s: %d", path, value); + } catch (IOException e) { + throw new UncheckedIOException("Failed to store integer in " + path, e); + } + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java index 9f7aaab2060..cbc8ffbf1b7 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java @@ -65,7 +65,7 @@ public abstract class ChildProcessException extends RuntimeException { if (possiblyHugeOutput.length() <= maxOutputPrefix + maxOutputSuffix + maxOutputSlack) { stringBuilder.append(possiblyHugeOutput); } else { - stringBuilder.append(possiblyHugeOutput.substring(0, maxOutputPrefix)) + stringBuilder.append(possiblyHugeOutput, 0, maxOutputPrefix) .append("... [") .append(possiblyHugeOutput.length() - maxOutputPrefix - maxOutputSuffix) .append(" chars omitted] ...") diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java index d88c6f4ab33..5d60823d1c5 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java @@ -36,6 +36,10 @@ public class Yum { return newYumCommand("install", packages, INSTALL_NOOP_PATTERN); } + /** + * @param packages A list of packages, each package being of the form name-1.2.3-1.el7.noarch, + * if no packages are given, will upgrade all installed packages + */ public GenericYumCommand upgrade(String... packages) { return newYumCommand("upgrade", packages, UPGRADE_NOOP_PATTERN); } @@ -70,7 +74,7 @@ public class Yum { this.packages = packages; this.commandOutputNoopPattern = commandOutputNoopPattern; - if (packages.isEmpty()) { + if (packages.isEmpty() && ! "upgrade".equals(yumCommand)) { throw new IllegalArgumentException("No packages specified"); } } diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java index db14efdd5d2..a1af36f9c21 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java @@ -11,10 +11,6 @@ import com.yahoo.vespa.hosted.provision.Node; import org.junit.Ignore; import org.junit.Test; -import java.util.Optional; - -import static org.hamcrest.core.Is.is; -import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.Assert.assertTrue; /** diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java index 91c61623ee7..c348dc4c8b5 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -152,7 +153,7 @@ public class NodeAdminImplTest { assertTrue(nodeAdmin.isFrozen()); assertTrue(nodeAdmin.subsystemFreezeDuration().isZero()); clock.advance(Duration.ofSeconds(1)); - assertTrue(nodeAdmin.subsystemFreezeDuration().equals(Duration.ofSeconds(1))); + assertEquals(Duration.ofSeconds(1), nodeAdmin.subsystemFreezeDuration()); // Unfreezing floors freeze duration assertTrue(nodeAdmin.setFrozen(false)); // Unfreeze everything @@ -164,7 +165,7 @@ public class NodeAdminImplTest { assertTrue(nodeAdmin.setFrozen(true)); assertTrue(nodeAdmin.subsystemFreezeDuration().isZero()); clock.advance(Duration.ofSeconds(1)); - assertTrue(nodeAdmin.subsystemFreezeDuration().equals(Duration.ofSeconds(1))); + assertEquals(Duration.ofSeconds(1), nodeAdmin.subsystemFreezeDuration()); } @Test diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectoryTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectoryTest.java index a83f3bbe7d4..b714ab539f6 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectoryTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/MakeDirectoryTest.java @@ -25,7 +25,7 @@ public class MakeDirectoryTest { private final FileSystem fileSystem = TestFileSystem.create(); private final TestTaskContext context = new TestTaskContext(); - private String path = "/parent/dir"; + private final String path = "/parent/dir"; private String permissions = "rwxr----x"; private String owner = "test-owner"; private String group = "test-group"; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java index 5bc45d7540e..a5eb0ab059b 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java @@ -104,7 +104,6 @@ public class CommandLineTest { @Test public void programFails() { - TestChildProcess2 child = new TestChildProcess2(0, ""); terminal.expectCommand("foo 2>&1", 1, ""); try { commandLine.add("foo").execute(); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java index d29d8741438..7f37336db70 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumTest.java @@ -9,6 +9,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -16,6 +17,7 @@ import static org.mockito.Mockito.mock; public class YumTest { private final TaskContext taskContext = mock(TaskContext.class); private final TestTerminal terminal = new TestTerminal(); + private final Yum yum = new Yum(terminal); @Before public void tearDown() { @@ -29,7 +31,6 @@ public class YumTest { 0, "foobar\nNothing to do\n"); - Yum yum = new Yum(terminal); assertFalse(yum .install("package-1", "package-2") .enableRepo("repo-name") @@ -43,7 +44,7 @@ public class YumTest { 0, "foobar\nNo packages marked for update\n"); - assertFalse(new Yum(terminal) + assertFalse(yum .upgrade("package-1", "package-2") .converge(taskContext)); } @@ -55,7 +56,7 @@ public class YumTest { 0, "foobar\nNo Packages marked for removal\n"); - assertFalse(new Yum(terminal) + assertFalse(yum .remove("package-1", "package-2") .converge(taskContext)); } @@ -67,7 +68,6 @@ public class YumTest { 0, "installing, installing"); - Yum yum = new Yum(terminal); assertTrue(yum .install("package-1", "package-2") .converge(taskContext)); @@ -80,7 +80,6 @@ public class YumTest { 0, "installing, installing"); - Yum yum = new Yum(terminal); assertTrue(yum .install("package-1", "package-2") .enableRepo("repo-name") @@ -94,7 +93,6 @@ public class YumTest { 1, "error"); - Yum yum = new Yum(terminal); yum.install("package-1", "package-2") .enableRepo("repo-name") .converge(taskContext); @@ -112,15 +110,26 @@ public class YumTest { "No package package-2 available.\n" + "Nothing to do\n"); - Yum yum = new Yum(terminal); Yum.GenericYumCommand install = yum.install("package-1", "package-2", "package-3"); try { install.converge(taskContext); fail(); } catch (Exception e) { - assertTrue(e.getCause() != null); + assertNotNull(e.getCause()); assertEquals("Unknown package: package-1", e.getCause().getMessage()); } } + + @Test(expected = IllegalArgumentException.class) + public void throwIfNoPackagesSpecified() { + yum.install(); + } + + @Test + public void allowToCallUpgradeWithNoPackages() { + terminal.expectCommand("yum upgrade --assumeyes 2>&1", 0, "OK"); + + yum.upgrade().converge(taskContext); + } }
\ No newline at end of file diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java index 3b7c4857f48..62dc9c2395f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ApplicationMaintainer.java @@ -11,12 +11,15 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; 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.concurrent.Executor; -import java.util.concurrent.Executors; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -26,11 +29,15 @@ import java.util.stream.Collectors; public abstract class ApplicationMaintainer extends Maintainer { private final Deployer deployer; + private final List<ApplicationId> pendingDeployments = new CopyOnWriteArrayList<>(); // Use a fixed thread pool to avoid overload on config servers. Resource usage when deploying varies // a lot between applications, so doing one by one avoids issues where one or more resource-demanding // deployments happen simultaneously - private final Executor deploymentExecutor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("node repo application maintainer")); + private final ThreadPoolExecutor deploymentExecutor = new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new DaemonThreadFactory("node repo application maintainer")); protected ApplicationMaintainer(Deployer deployer, NodeRepository nodeRepository, Duration interval, JobControl jobControl) { super(nodeRepository, interval, jobControl); @@ -39,12 +46,15 @@ public abstract class ApplicationMaintainer extends Maintainer { @Override protected final void maintain() { - Set<ApplicationId> applications = applicationsNeedingMaintenance(); - for (ApplicationId application : applications) { - deploy(application); - } + applicationsNeedingMaintenance().forEach(this::deploy); + } + + /** Returns the number of deployments that are pending execution */ + public int pendingDeployments() { + return pendingDeployments.size(); } + /** Returns whether given application should be deployed at this moment in time */ protected boolean canDeployNow(ApplicationId application) { return true; } @@ -56,16 +66,21 @@ public abstract class ApplicationMaintainer extends Maintainer { * even when deployments are slow. */ protected void deploy(ApplicationId application) { + if (pendingDeployments.contains(application)) { + return;// Avoid queuing multiple deployments for same application + } + log.log(LogLevel.INFO, application + " will be deployed, last deploy time " + + getLastDeployTime(application)); deploymentExecutor.execute(() -> deployWithLock(application)); + pendingDeployments.add(application); } protected Deployer deployer() { return deployer; } - protected Set<ApplicationId> applicationsNeedingMaintenance() { return nodesNeedingMaintenance().stream() - .map(node -> node.allocation().get().owner()) - .collect(Collectors.toCollection(LinkedHashSet::new)); + .map(node -> node.allocation().get().owner()) + .collect(Collectors.toCollection(LinkedHashSet::new)); } /** @@ -75,7 +90,7 @@ public abstract class ApplicationMaintainer extends Maintainer { protected abstract List<Node> nodesNeedingMaintenance(); /** Redeploy this application. A lock will be taken for the duration of the deployment activation */ - final void deployWithLock(ApplicationId application) { + protected final void deployWithLock(ApplicationId application) { // An application might change its state between the time the set of applications is retrieved and the // time deployment happens. Lock the application and check if it's still active. // @@ -89,12 +104,31 @@ public abstract class ApplicationMaintainer extends Maintainer { deployment.get().activate(); } catch (RuntimeException e) { log.log(LogLevel.WARNING, "Exception on maintenance redeploy", e); + } finally { + pendingDeployments.remove(application); } } + /** Returns the last time application was deployed. Epoch is returned if the application has never been deployed. */ + protected final Instant getLastDeployTime(ApplicationId application) { + return deployer.lastDeployTime(application).orElse(Instant.EPOCH); + } + /** Returns true when application has at least one active node */ private boolean isActive(ApplicationId application) { return ! nodeRepository().getNodes(application, Node.State.active).isEmpty(); } + @Override + public void deconstruct() { + super.deconstruct(); + this.deploymentExecutor.shutdownNow(); + try { + // Give deployments in progress some time to complete + this.deploymentExecutor.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java index ee5d6a04ddc..8b2d0a55cd8 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java @@ -3,17 +3,18 @@ package com.yahoo.vespa.hosted.provision.maintenance; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Deployer; -import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * The application maintainer regularly redeploys all applications to make sure the node repo and application @@ -23,14 +24,17 @@ import java.util.Set; * @author bratseth */ public class PeriodicApplicationMaintainer extends ApplicationMaintainer { + private final Duration minTimeBetweenRedeployments; + private final Clock clock; private final Instant start; public PeriodicApplicationMaintainer(Deployer deployer, NodeRepository nodeRepository, Duration interval, Duration minTimeBetweenRedeployments, JobControl jobControl) { super(deployer, nodeRepository, interval, jobControl); this.minTimeBetweenRedeployments = minTimeBetweenRedeployments; - this.start = Instant.now(); + this.clock = nodeRepository.clock(); + this.start = clock.instant(); } @Override @@ -39,24 +43,17 @@ public class PeriodicApplicationMaintainer extends ApplicationMaintainer { return getLastDeployTime(application).isBefore(nodeRepository().clock().instant().minus(minTimeBetweenRedeployments)); } - // Returns the app that was deployed the longest time ago + // Returns the applications that need to be redeployed by this config server at this point in time. @Override protected Set<ApplicationId> applicationsNeedingMaintenance() { if (waitInitially()) return Collections.emptySet(); - Optional<ApplicationId> app = (nodesNeedingMaintenance().stream() - .map(node -> node.allocation().get().owner()) - .distinct() - .filter(this::shouldBeDeployedOnThisServer) - .min(Comparator.comparing(this::getLastDeployTime))) - .filter(this::canDeployNow); - app.ifPresent(applicationId -> log.log(LogLevel.INFO, applicationId + " will be deployed, last deploy time " + - getLastDeployTime(applicationId))); - return app.map(Collections::singleton).orElseGet(Collections::emptySet); - } - - private Instant getLastDeployTime(ApplicationId application) { - return deployer().lastDeployTime(application).orElse(Instant.EPOCH); + return nodesNeedingMaintenance().stream() + .map(node -> node.allocation().get().owner()) + .filter(this::shouldBeDeployedOnThisServer) + .filter(this::canDeployNow) + .sorted(Comparator.comparing(this::getLastDeployTime)) + .collect(Collectors.toCollection(LinkedHashSet::new)); } // We only know last deploy time for applications that were deployed on this config server, @@ -66,8 +63,8 @@ public class PeriodicApplicationMaintainer extends ApplicationMaintainer { } // TODO: Do not start deploying until some time has gone (ideally only until bootstrap of config server is finished) - protected boolean waitInitially() { - return Instant.now().isBefore(start.plus(minTimeBetweenRedeployments)); + private boolean waitInitially() { + return clock.instant().isBefore(start.plus(minTimeBetweenRedeployments)); } @Override diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java index 99beab50e16..299dc66c547 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; /** @@ -35,6 +36,7 @@ public class MockDeployer implements Deployer { public int redeployments = 0; private final Clock clock; + private final ReentrantLock lock = new ReentrantLock(); @Inject @SuppressWarnings("unused") @@ -54,6 +56,10 @@ public class MockDeployer implements Deployer { this.applications = applications; } + public ReentrantLock lock() { + return lock; + } + @Override public Optional<Deployment> deployFromLocalActive(ApplicationId id, boolean bootstrap) { return deployFromLocalActive(id, Duration.ofSeconds(60)); @@ -61,8 +67,17 @@ public class MockDeployer implements Deployer { @Override public Optional<Deployment> deployFromLocalActive(ApplicationId id, Duration timeout) { - lastDeployTimes.put(id, clock.instant()); - return Optional.of(new MockDeployment(provisioner, applications.get(id))); + try { + lock.lockInterruptibly(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + lastDeployTimes.put(id, clock.instant()); + return Optional.of(new MockDeployment(provisioner, applications.get(id))); + } finally { + lock.unlock(); + } } @Override diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java index 2ddb2e0d004..3a7bca801a9 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java @@ -29,7 +29,9 @@ import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; +import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.time.Duration; @@ -41,36 +43,48 @@ import java.util.Map; import java.util.Optional; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /** * @author bratseth */ +@Ignore public class PeriodicApplicationMaintainerTest { private static final NodeFlavors nodeFlavors = FlavorConfigBuilder.createDummies("default"); private NodeRepository nodeRepository; private Fixture fixture; + private ManualClock clock; @Before public void before() { Curator curator = new MockCurator(); Zone zone = new Zone(Environment.prod, RegionName.from("us-east")); - this.nodeRepository = new NodeRepository(nodeFlavors, curator, new ManualClock(), zone, + this.clock = new ManualClock(); + this.nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone, new MockNameResolver().mockAnyLookup(), new DockerImage("docker-registry.domain.tld:8080/dist/vespa"), true); this.fixture = new Fixture(zone, nodeRepository, nodeFlavors, curator); - } - @Test - public void test_application_maintenance() { createReadyNodes(15, nodeRepository, nodeFlavors); createHostNodes(2, nodeRepository, nodeFlavors); + } + + @After + public void after() { + this.fixture.maintainer.deconstruct(); + } + @Test(timeout = 60_000) + public void test_application_maintenance() { // Create applications fixture.activate(); + // Exhaust initial wait period + clock.advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1))); + // Fail and park some nodes nodeRepository.fail(nodeRepository.getNodes(fixture.app1).get(3).hostname(), Agent.system, "Failing to unit test"); nodeRepository.fail(nodeRepository.getNodes(fixture.app2).get(0).hostname(), Agent.system, "Failing to unit test"); @@ -102,7 +116,7 @@ public class PeriodicApplicationMaintainerTest { 0, fixture.getNodes(Node.State.active).retired().size()); // Cause maintenance deployment which will update the applications with the re-activated nodes - ((ManualClock)nodeRepository.clock()).advance(Duration.ofMinutes(35)); // Otherwise redeploys are inhibited + clock.advance(Duration.ofMinutes(35)); // Otherwise redeploys are inhibited fixture.runApplicationMaintainer(); assertEquals("Superflous content nodes are retired", reactivatedInApp2, fixture.getNodes(Node.State.active).retired().size()); @@ -110,11 +124,8 @@ public class PeriodicApplicationMaintainerTest { reactivatedInApp1, fixture.getNodes(Node.State.inactive).size()); } - @Test + @Test(timeout = 60_000) public void deleted_application_is_not_reactivated() { - createReadyNodes(15, nodeRepository, nodeFlavors); - createHostNodes(2, nodeRepository, nodeFlavors); - // Create applications fixture.activate(); @@ -126,36 +137,81 @@ public class PeriodicApplicationMaintainerTest { assertEquals(fixture.wantedNodesApp2, nodeRepository.getNodes(fixture.app2, Node.State.inactive).size()); // Nodes belonging to app2 are inactive after maintenance - fixture.runApplicationMaintainer(Optional.of(frozenActiveNodes)); + fixture.maintainer.setOverriddenNodesNeedingMaintenance(frozenActiveNodes); + fixture.runApplicationMaintainer(); assertEquals("Inactive nodes were incorrectly activated after maintenance", fixture.wantedNodesApp2, nodeRepository.getNodes(fixture.app2, Node.State.inactive).size()); } - @Test + @Test(timeout = 60_000) public void application_deploy_inhibits_redeploy_for_a_while() { - ManualClock clock = (ManualClock)nodeRepository.clock(); - createReadyNodes(15, nodeRepository, nodeFlavors); - createHostNodes(2, nodeRepository, nodeFlavors); - - // Create applications fixture.activate(); + + // Holds off on deployments a while after starting + fixture.runApplicationMaintainer(); + assertFalse("No deployment expected", fixture.deployer.lastDeployTime(fixture.app1).isPresent()); + assertFalse("No deployment expected", fixture.deployer.lastDeployTime(fixture.app2).isPresent()); + // Exhaust initial wait period + clock.advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1))); + + // First deployment of applications fixture.runApplicationMaintainer(); Instant firstDeployTime = clock.instant(); assertEquals(firstDeployTime, fixture.deployer.lastDeployTime(fixture.app1).get()); assertEquals(firstDeployTime, fixture.deployer.lastDeployTime(fixture.app2).get()); - ((ManualClock) nodeRepository.clock()).advance(Duration.ofMinutes(5)); + clock.advance(Duration.ofMinutes(5)); fixture.runApplicationMaintainer(); // Too soon: Not redeployed: assertEquals(firstDeployTime, fixture.deployer.lastDeployTime(fixture.app1).get()); assertEquals(firstDeployTime, fixture.deployer.lastDeployTime(fixture.app2).get()); - ((ManualClock) nodeRepository.clock()).advance(Duration.ofMinutes(30)); + clock.advance(Duration.ofMinutes(30)); fixture.runApplicationMaintainer(); // Redeployed: assertEquals(clock.instant(), fixture.deployer.lastDeployTime(fixture.app1).get()); assertEquals(clock.instant(), fixture.deployer.lastDeployTime(fixture.app2).get()); } + @Test(timeout = 60_000) + public void queues_all_eligible_applications_for_deployment() throws Exception { + fixture.activate(); + + // Exhaust initial wait period + clock.advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1))); + + // Lock deployer to simulate slow deployments + fixture.deployer.lock().lockInterruptibly(); + + try { + // Queues all eligible applications + assertEquals(2, fixture.maintainer.applicationsNeedingMaintenance().size()); + fixture.runApplicationMaintainer(false); + assertEquals(2, fixture.maintainer.pendingDeployments()); + + // Enough time passes to make applications eligible for another periodic deployment + clock.advance(Duration.ofMinutes(30).plus(Duration.ofSeconds(1))); + fixture.runApplicationMaintainer(false); + + // Deployments are not re-queued as previous deployments are still pending + assertEquals(2, fixture.maintainer.pendingDeployments()); + + // Slow deployments complete + fixture.deployer.lock().unlock(); + fixture.runApplicationMaintainer(); + Instant deployTime = clock.instant(); + assertEquals(deployTime, fixture.deployer.lastDeployTime(fixture.app1).get()); + assertEquals(deployTime, fixture.deployer.lastDeployTime(fixture.app2).get()); + + // Too soon: Already deployed recently + clock.advance(Duration.ofMinutes(5)); + assertEquals(0, fixture.maintainer.applicationsNeedingMaintenance().size()); + } finally { + if (fixture.deployer.lock().isHeldByCurrentThread()) { + fixture.deployer.lock().unlock(); + } + } + } + private void createReadyNodes(int count, NodeRepository nodeRepository, NodeFlavors nodeFlavors) { List<Node> nodes = new ArrayList<>(count); for (int i = 0; i < count; i++) @@ -179,7 +235,7 @@ public class PeriodicApplicationMaintainerTest { final NodeRepository nodeRepository; final NodeRepositoryProvisioner provisioner; final Curator curator; - final Deployer deployer; + final MockDeployer deployer; final ApplicationId app1 = ApplicationId.from(TenantName.from("foo1"), ApplicationName.from("bar"), InstanceName.from("fuz")); final ApplicationId app2 = ApplicationId.from(TenantName.from("foo2"), ApplicationName.from("bar"), InstanceName.from("fuz")); @@ -188,6 +244,8 @@ public class PeriodicApplicationMaintainerTest { final int wantedNodesApp1 = 5; final int wantedNodesApp2 = 7; + private final TestablePeriodicApplicationMaintainer maintainer; + Fixture(Zone zone, NodeRepository nodeRepository, NodeFlavors flavors, Curator curator) { this.nodeRepository = nodeRepository; this.curator = curator; @@ -199,6 +257,8 @@ public class PeriodicApplicationMaintainerTest { apps.put(app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.fromNodeCount(wantedNodesApp2, Optional.of("default"), false, true), 1)); this.deployer = new MockDeployer(provisioner, nodeRepository.clock(), apps); + this.maintainer = new TestablePeriodicApplicationMaintainer(deployer, nodeRepository, Duration.ofDays(1), // Long duration to prevent scheduled runs during test + Duration.ofMinutes(30)); } void activate() { @@ -222,16 +282,12 @@ public class PeriodicApplicationMaintainerTest { } void runApplicationMaintainer() { - runApplicationMaintainer(Optional.empty()); + runApplicationMaintainer(true); } - void runApplicationMaintainer(Optional<List<Node>> overriddenNodesNeedingMaintenance) { - TestablePeriodicApplicationMaintainer maintainer = - new TestablePeriodicApplicationMaintainer(deployer, nodeRepository, Duration.ofMinutes(1), - Duration.ofMinutes(30), overriddenNodesNeedingMaintenance); - // Need to run twice, as only one app is deployed per run - maintainer.run(); + void runApplicationMaintainer(boolean waitForDeployments) { maintainer.run(); + while (waitForDeployments && fixture.maintainer.pendingDeployments() != 0); } NodeList getNodes(Node.State ... states) { @@ -240,36 +296,32 @@ public class PeriodicApplicationMaintainerTest { } - public static class TestablePeriodicApplicationMaintainer extends PeriodicApplicationMaintainer { + private static class TestablePeriodicApplicationMaintainer extends PeriodicApplicationMaintainer { - private Optional<List<Node>> overriddenNodesNeedingMaintenance; - - TestablePeriodicApplicationMaintainer(Deployer deployer, NodeRepository nodeRepository, Duration interval, - Duration minTimeBetweenRedeployments, Optional<List<Node>> overriddenNodesNeedingMaintenance) { - super(deployer, nodeRepository, interval, minTimeBetweenRedeployments, new JobControl(nodeRepository.database())); + private List<Node> overriddenNodesNeedingMaintenance; + + TestablePeriodicApplicationMaintainer setOverriddenNodesNeedingMaintenance(List<Node> overriddenNodesNeedingMaintenance) { this.overriddenNodesNeedingMaintenance = overriddenNodesNeedingMaintenance; + return this; } - @Override - protected void deploy(ApplicationId application) { - deployWithLock(application); + TestablePeriodicApplicationMaintainer(Deployer deployer, NodeRepository nodeRepository, Duration interval, + Duration minTimeBetweenRedeployments) { + super(deployer, nodeRepository, interval, minTimeBetweenRedeployments, new JobControl(nodeRepository.database())); } @Override protected List<Node> nodesNeedingMaintenance() { - if (overriddenNodesNeedingMaintenance.isPresent()) - return overriddenNodesNeedingMaintenance.get(); - return super.nodesNeedingMaintenance(); + return overriddenNodesNeedingMaintenance != null + ? overriddenNodesNeedingMaintenance + : super.nodesNeedingMaintenance(); } + @Override protected boolean shouldBeDeployedOnThisServer(ApplicationId application) { return true; } - protected boolean waitInitially() { - return false; - } - } } diff --git a/searchcore/CMakeLists.txt b/searchcore/CMakeLists.txt index 89e6493cfbc..9d996d96dc7 100644 --- a/searchcore/CMakeLists.txt +++ b/searchcore/CMakeLists.txt @@ -129,6 +129,7 @@ vespa_define_module( src/tests/proton/proton src/tests/proton/proton_config_fetcher src/tests/proton/proton_configurer + src/tests/proton/proton_disk_layout src/tests/proton/reference/gid_to_lid_change_handler src/tests/proton/reference/gid_to_lid_change_listener src/tests/proton/reference/gid_to_lid_change_registrator diff --git a/searchcore/src/tests/proton/attribute/attribute_directory/attribute_directory_test.cpp b/searchcore/src/tests/proton/attribute/attribute_directory/attribute_directory_test.cpp index 37cf201e354..f95ea478ce1 100644 --- a/searchcore/src/tests/proton/attribute/attribute_directory/attribute_directory_test.cpp +++ b/searchcore/src/tests/proton/attribute/attribute_directory/attribute_directory_test.cpp @@ -137,6 +137,7 @@ struct Fixture : public DirectoryHandler EXPECT_TRUE(hasAttributeDir(dir)); auto writer = dir->getWriter(); writer->createInvalidSnapshot(serialNum); + vespalib::mkdir(writer->getSnapshotDir(serialNum), false); writer->markValidSnapshot(serialNum); TEST_DO(assertAttributeDiskDir("foo")); } @@ -162,6 +163,7 @@ struct Fixture : public DirectoryHandler auto dir = createFooAttrDir(); auto writer = dir->getWriter(); writer->createInvalidSnapshot(serialNum); + vespalib::mkdir(writer->getSnapshotDir(serialNum), false); writer->markValidSnapshot(serialNum); } @@ -208,8 +210,10 @@ TEST_F("Test that we can prune attribute snapshots", Fixture) TEST_DO(f.assertNotAttributeDiskDir("foo")); auto writer = dir->getWriter(); writer->createInvalidSnapshot(2); + vespalib::mkdir(writer->getSnapshotDir(2), false); writer->markValidSnapshot(2); writer->createInvalidSnapshot(4); + vespalib::mkdir(writer->getSnapshotDir(4), false); writer->markValidSnapshot(4); writer.reset(); TEST_DO(f.assertAttributeDiskDir("foo")); diff --git a/searchcore/src/tests/proton/proton_disk_layout/CMakeLists.txt b/searchcore/src/tests/proton/proton_disk_layout/CMakeLists.txt new file mode 100644 index 00000000000..f63fa21a954 --- /dev/null +++ b/searchcore/src/tests/proton/proton_disk_layout/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(searchcore_proton_disk_layout_test_app TEST + SOURCES + proton_disk_layout_test.cpp + DEPENDS + searchcore_server + searchcore_fconfig +) +vespa_add_test(NAME searchcore_proton_disk_layout_test_app COMMAND searchcore_proton_disk_layout_test_app) diff --git a/searchcore/src/tests/proton/proton_disk_layout/proton_disk_layout_test.cpp b/searchcore/src/tests/proton/proton_disk_layout/proton_disk_layout_test.cpp new file mode 100644 index 00000000000..edb4250ce76 --- /dev/null +++ b/searchcore/src/tests/proton/proton_disk_layout/proton_disk_layout_test.cpp @@ -0,0 +1,178 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/searchcore/proton/server/proton_disk_layout.h> +#include <vespa/searchcore/proton/common/doctypename.h> +#include <vespa/searchlib/index/dummyfileheadercontext.h> +#include <vespa/searchlib/transactionlog/translogserver.h> +#include <vespa/searchlib/transactionlog/translogclient.h> +#include <vespa/vespalib/io/fileutil.h> +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/test/insertion_operators.h> +#include <vespa/vespalib/util/stringfmt.h> + +using search::index::DummyFileHeaderContext; +using search::transactionlog::TransLogClient; +using search::transactionlog::TransLogServer; +using proton::DocTypeName; +using proton::ProtonDiskLayout; + +static constexpr unsigned int tlsPort = 9018; + +static const vespalib::string baseDir("testdb"); +static const vespalib::string documentsDir(baseDir + "/documents"); + +struct FixtureBase +{ + FixtureBase() { vespalib::rmdir(baseDir, true); } + ~FixtureBase() { vespalib::rmdir(baseDir, true); } +}; + +struct DiskLayoutFixture { + DummyFileHeaderContext _fileHeaderContext; + TransLogServer _tls; + vespalib::string _tlsSpec; + ProtonDiskLayout _diskLayout; + + DiskLayoutFixture(); + ~DiskLayoutFixture(); + + void createDirs(const std::set<vespalib::string> &dirs) { + for (const auto &dir : dirs) { + vespalib::mkdir(documentsDir + "/" + dir, false); + } + } + void createDomains(const std::set<vespalib::string> &domains) { + TransLogClient tlc(_tlsSpec); + for (const auto &domain : domains) { + ASSERT_TRUE(tlc.create(domain)); + } + } + + std::set<vespalib::string> listDomains() { + std::vector<vespalib::string> domainVector; + TransLogClient tlc(_tlsSpec); + ASSERT_TRUE(tlc.listDomains(domainVector)); + std::set<vespalib::string> domains; + for (const auto &domain : domainVector) { + domains.emplace(domain); + } + return domains; + } + + std::set<vespalib::string> listDirs() { + std::set<vespalib::string> dirs; + auto names = vespalib::listDirectory(documentsDir); + for (const auto &name : names) { + if (vespalib::isDirectory(documentsDir + "/" + name)) { + dirs.emplace(name); + } + } + return dirs; + } + + void initAndPruneUnused(const std::set<vespalib::string> names) + { + std::set<DocTypeName> docTypeNames; + for (const auto &name: names) { + docTypeNames.emplace(name); + } + _diskLayout.initAndPruneUnused(docTypeNames); + } + + void assertDirs(const std::set<vespalib::string> &expDirs) { + EXPECT_EQUAL(expDirs, listDirs()); + } + + void assertDomains(const std::set<vespalib::string> &expDomains) + { + EXPECT_EQUAL(expDomains, listDomains()); + } +}; + +DiskLayoutFixture::DiskLayoutFixture() + : _fileHeaderContext(), + _tls("tls", tlsPort, baseDir, _fileHeaderContext), + _tlsSpec(vespalib::make_string("tcp/localhost:%u", tlsPort)), + _diskLayout(baseDir, _tlsSpec) +{ +} + +DiskLayoutFixture::~DiskLayoutFixture() = default; + +struct Fixture : public FixtureBase, public DiskLayoutFixture +{ + Fixture() + : FixtureBase(), + DiskLayoutFixture() + { + } +}; + +TEST_F("require that empty config is ok", Fixture) { + TEST_DO(f.assertDirs({})); + TEST_DO(f.assertDomains({})); +} + +TEST_F("require that disk layout is preserved", FixtureBase) +{ + { + DiskLayoutFixture diskLayout; + diskLayout.createDirs({"foo", "bar"}); + diskLayout.createDomains({"bar", "baz"}); + } + { + DiskLayoutFixture diskLayout; + TEST_DO(diskLayout.assertDirs({"foo", "bar"})); + TEST_DO(diskLayout.assertDomains({"bar", "baz"})); + } +} + +TEST_F("require that used dir is preserved", Fixture) +{ + f.createDirs({"foo"}); + f.createDomains({"foo"}); + f.initAndPruneUnused({"foo"}); + TEST_DO(f.assertDirs({"foo"})); + TEST_DO(f.assertDomains({"foo"})); +} + +TEST_F("require that unused dir is removed", Fixture) +{ + f.createDirs({"foo"}); + f.createDomains({"foo"}); + f.initAndPruneUnused({"bar"}); + TEST_DO(f.assertDirs({})); + TEST_DO(f.assertDomains({})); +} + +TEST_F("require that interrupted remove is completed", Fixture) +{ + f.createDirs({"foo.removed"}); + f.createDomains({"foo"}); + f.initAndPruneUnused({"foo"}); + TEST_DO(f.assertDirs({})); + TEST_DO(f.assertDomains({})); +} + +TEST_F("require that early interrupted remove is completed", Fixture) +{ + f.createDirs({"foo", "foo.removed"}); + f.createDomains({"foo"}); + f.initAndPruneUnused({"foo"}); + TEST_DO(f.assertDirs({})); + TEST_DO(f.assertDomains({})); +} + +TEST_F("require that live document db dir remove works", Fixture) +{ + f.createDirs({"foo"}); + f.createDomains({"foo"}); + f.initAndPruneUnused({"foo"}); + TEST_DO(f.assertDirs({"foo"})); + TEST_DO(f.assertDomains({"foo"})); + f._diskLayout.remove(DocTypeName("foo")); + TEST_DO(f.assertDirs({})); + TEST_DO(f.assertDomains({})); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/attribute_reprocessing_initializer_test.cpp b/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/attribute_reprocessing_initializer_test.cpp index 7be774f7291..72e558fd25f 100644 --- a/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/attribute_reprocessing_initializer_test.cpp +++ b/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/attribute_reprocessing_initializer_test.cpp @@ -294,9 +294,9 @@ TEST("require that added attribute aspect with flushed attribute after interrupt auto dir = diskLayout->createAttributeDir("a"); auto writer = dir->getWriter(); writer->createInvalidSnapshot(INIT_SERIAL_NUM); - writer->markValidSnapshot(INIT_SERIAL_NUM); auto snapshotdir = writer->getSnapshotDir(INIT_SERIAL_NUM); vespalib::mkdir(snapshotdir); + writer->markValidSnapshot(INIT_SERIAL_NUM); auto av = AttributeFactory::createAttribute(snapshotdir + "/a", Config(BasicType::STRING)); av->save(); diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_directory.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_directory.cpp index f2e4ac4905d..2bbc6c99dc0 100644 --- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_directory.cpp +++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_directory.cpp @@ -112,6 +112,7 @@ AttributeDirectory::createInvalidSnapshot(SerialNum serialNum) if (empty()) { vespalib::string dirName(getDirName()); vespalib::mkdir(dirName, false); + vespalib::File::sync(vespalib::dirname(dirName)); } { std::lock_guard<std::mutex> guard(_mutex); @@ -130,6 +131,9 @@ AttributeDirectory::markValidSnapshot(SerialNum serialNum) assert(snap.syncToken == serialNum); _snapInfo.validateSnapshot(serialNum); } + vespalib::string snapshotDir(getSnapshotDir(serialNum)); + vespalib::File::sync(snapshotDir); + vespalib::File::sync(dirname(snapshotDir)); saveSnapInfo(); } @@ -178,6 +182,7 @@ AttributeDirectory::removeInvalidSnapshots() vespalib::rmdir(subDir, true); } if (!toRemove.empty()) { + vespalib::File::sync(getDirName()); { std::lock_guard<std::mutex> guard(_mutex); for (const auto &serialNum : toRemove) { @@ -194,6 +199,7 @@ AttributeDirectory::removeDiskDir() if (empty()) { vespalib::string dirName(getDirName()); vespalib::rmdir(dirName, true); + vespalib::File::sync(vespalib::dirname(dirName)); return true; } return false; diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attributedisklayout.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attributedisklayout.cpp index bb2f99d077b..a675927b85f 100644 --- a/searchcore/src/vespa/searchcore/proton/attribute/attributedisklayout.cpp +++ b/searchcore/src/vespa/searchcore/proton/attribute/attributedisklayout.cpp @@ -14,6 +14,7 @@ AttributeDiskLayout::AttributeDiskLayout(const vespalib::string &baseDir, Privat _dirs() { vespalib::mkdir(_baseDir, false); + vespalib::File::sync(vespalib::dirname(_baseDir)); } AttributeDiskLayout::~AttributeDiskLayout() diff --git a/searchcore/src/vespa/searchcore/proton/index/index_manager_initializer.cpp b/searchcore/src/vespa/searchcore/proton/index/index_manager_initializer.cpp index b8f2947c9b4..ad233a66d1f 100644 --- a/searchcore/src/vespa/searchcore/proton/index/index_manager_initializer.cpp +++ b/searchcore/src/vespa/searchcore/proton/index/index_manager_initializer.cpp @@ -45,6 +45,7 @@ IndexManagerInitializer::run() LOG(debug, "About to create proton::IndexManager with %u index field(s)", _schema.getNumIndexFields()); vespalib::mkdir(_baseDir, false); + vespalib::File::sync(vespalib::dirname(_baseDir)); *_indexManager = std::make_shared<proton::IndexManager> (_baseDir, _warmupCfg, diff --git a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp index 00a7e9b9140..c2624719d81 100644 --- a/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp +++ b/searchcore/src/vespa/searchcore/proton/metrics/trans_log_server_metrics.cpp @@ -44,6 +44,7 @@ TransLogServerMetrics::considerRemoveDomains(const DomainStats &stats) for (auto itr = _domainMetrics.begin(); itr != _domainMetrics.end(); ) { const vespalib::string &documentType = itr->first; if (stats.find(documentType) == stats.end()) { + _parent->unregisterMetric(*itr->second); itr = _domainMetrics.erase(itr); } else { ++itr; diff --git a/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp index 76f2ffce93a..72f48df3295 100644 --- a/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/fileconfigmanager.cpp @@ -229,6 +229,7 @@ FileConfigManager::FileConfigManager(const vespalib::string &baseDir, _protonConfig() { vespalib::mkdir(baseDir, false); + vespalib::File::sync(vespalib::dirname(baseDir)); if (!_info.load()) _info.save(); removeInvalid(); @@ -297,6 +298,8 @@ FileConfigManager::saveConfig(const DocumentDBConfig &snapshot, bool saveHistorySchemaRes = historySchema.saveToFile(snapDir + "/historyschema.txt"); assert(saveHistorySchemaRes); (void) saveHistorySchemaRes; + vespalib::File::sync(snapDir); + vespalib::File::sync(_baseDir); _info.validateSnapshot(serialNum); @@ -402,6 +405,7 @@ FileConfigManager::removeInvalid() LOG(warning, "Removing obsolete config directory '%s' failed due to %s", snapDir.c_str(), e.what()); } } + vespalib::File::sync(_baseDir); for (const auto &serial : toRem) { _info.removeSnapshot(serial); } diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_disk_layout.cpp b/searchcore/src/vespa/searchcore/proton/server/proton_disk_layout.cpp index 1bc24d4a0e3..31fd44eec5e 100644 --- a/searchcore/src/vespa/searchcore/proton/server/proton_disk_layout.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/proton_disk_layout.cpp @@ -3,15 +3,75 @@ #include "proton_disk_layout.h" #include <vespa/vespalib/io/fileutil.h> #include <vespa/fastos/file.h> +#include <vespa/searchcore/proton/common/doctypename.h> +#include <vespa/searchlib/transactionlog/translogclient.h> #include <cassert> +#include <vespa/log/log.h> +LOG_SETUP(".proton.server.proton_disk_layout"); + +using search::transactionlog::TransLogClient; + namespace proton { +namespace { + +struct DocumentDBDirMeta +{ + bool normal; + bool removed; + + DocumentDBDirMeta() + : normal(false), + removed(false) + { + } +}; + +using DocumentDBDirScan = std::map<DocTypeName, DocumentDBDirMeta>; + +vespalib::string getDocumentsDir(const vespalib::string &baseDir) +{ + return baseDir + "/documents"; +} + +vespalib::string removedSuffix(".removed"); + +vespalib::string getNormalName(const vespalib::string removedName) { + return removedName.substr(0, removedName.size() - removedSuffix.size()); +} + +vespalib::string getRemovedName(const vespalib::string &normalName) +{ + return normalName + removedSuffix; +} + +bool isRemovedName(const vespalib::string &dirName) +{ + return dirName.size() > removedSuffix.size() && dirName.substr(dirName.size() - removedSuffix.size()) == removedSuffix; +} + +void scanDir(const vespalib::string documentsDir, DocumentDBDirScan &dirs) +{ + auto names = vespalib::listDirectory(documentsDir); + for (const auto &name : names) { + if (vespalib::isDirectory(documentsDir + "/" + name)) { + if (isRemovedName(name)) { + dirs[DocTypeName(getNormalName(name))].removed = true; + } else { + dirs[DocTypeName(name)].normal = true; + } + } + } +} + +} + ProtonDiskLayout::ProtonDiskLayout(const vespalib::string &baseDir, const vespalib::string &tlsSpec) : _baseDir(baseDir), _tlsSpec(tlsSpec) { - vespalib::mkdir(_baseDir + "/documents", true); + vespalib::mkdir(getDocumentsDir(_baseDir), true); } ProtonDiskLayout::~ProtonDiskLayout() = default; @@ -19,13 +79,41 @@ ProtonDiskLayout::~ProtonDiskLayout() = default; void ProtonDiskLayout::remove(const DocTypeName &docTypeName) { - (void) docTypeName; + vespalib::string documentsDir(getDocumentsDir(_baseDir)); + vespalib::string name(docTypeName.toString()); + vespalib::string normalDir(documentsDir + "/" + name); + vespalib::string removedDir(documentsDir + "/" + getRemovedName(name)); + vespalib::rename(normalDir, removedDir, false, false); + vespalib::File::sync(documentsDir); + TransLogClient tlc(_tlsSpec); + if (!tlc.remove(name)) { + LOG(fatal, "Failed to remove tls domain %s", name.c_str()); + LOG_ABORT("Failed to remove tls domain"); + } + vespalib::rmdir(removedDir, true); + vespalib::File::sync(documentsDir); } void ProtonDiskLayout::initAndPruneUnused(const std::set<DocTypeName> &docTypeNames) { - (void) docTypeNames; + vespalib::string documentsDir(getDocumentsDir(_baseDir)); + DocumentDBDirScan dirs; + scanDir(documentsDir, dirs); + for (const auto &dir : dirs) { + if (dir.second.removed) { + // Complete interrupted removal + if (dir.second.normal) { + vespalib::string name(dir.first.toString()); + vespalib::string normalDir(documentsDir + "/" + name); + vespalib::rmdir(normalDir, true); + } + remove(dir.first); + } else if (docTypeNames.count(dir.first) == 0) { + // Remove unused directory + remove(dir.first); + } + } } } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp index ad06525a5a3..2d3c204d259 100644 --- a/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/storeonlydocsubdb.cpp @@ -139,6 +139,7 @@ StoreOnlyDocSubDB::StoreOnlyDocSubDB(const Config &cfg, const Context &ctx) _gidToLidChangeHandler(std::make_shared<DummyGidToLidChangeHandler>()) { vespalib::mkdir(_baseDir, false); // Assume parent is created. + vespalib::File::sync(vespalib::dirname(_baseDir)); } StoreOnlyDocSubDB::~StoreOnlyDocSubDB() diff --git a/searchcorespi/src/vespa/searchcorespi/index/diskindexcleaner.cpp b/searchcorespi/src/vespa/searchcorespi/index/diskindexcleaner.cpp index c363a93815b..e0cb8548f0d 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/diskindexcleaner.cpp +++ b/searchcorespi/src/vespa/searchcorespi/index/diskindexcleaner.cpp @@ -3,6 +3,7 @@ #include "diskindexcleaner.h" #include "activediskindexes.h" #include <vespa/fastos/file.h> +#include <vespa/vespalib/io/fileutil.h> #include <sstream> #include <vector> @@ -34,6 +35,11 @@ bool isValidIndex(const string &index_dir) { return serial_file.OpenReadOnlyExisting(); } +void invalidateIndex(const string &index_dir) { + vespalib::unlink(index_dir + "/serial.dat"); + vespalib::File::sync(index_dir); +} + uint32_t findLastFusionId(const string &base_dir, const vector<string> &indexes) { uint32_t fusion_id = 0; @@ -56,7 +62,8 @@ uint32_t findLastFusionId(const string &base_dir, void removeDir(const string &dir) { LOG(debug, "Removing index dir '%s'", dir.c_str()); - FastOS_FileInterface::EmptyAndRemoveDirectory(dir.c_str()); + invalidateIndex(dir); + vespalib::rmdir(dir, true); } bool isOldIndex(const string &index, uint32_t last_fusion_id) { @@ -73,14 +80,18 @@ bool isOldIndex(const string &index, uint32_t last_fusion_id) { } void removeOld(const string &base_dir, const vector<string> &indexes, - const ActiveDiskIndexes &active_indexes) { + const ActiveDiskIndexes &active_indexes, bool remove) { uint32_t last_fusion_id = findLastFusionId(base_dir, indexes); for (size_t i = 0; i < indexes.size(); ++i) { const string index_dir = base_dir + "/" + indexes[i]; if (isOldIndex(indexes[i], last_fusion_id) && !active_indexes.isActive(index_dir)) { - removeDir(index_dir); + if (remove) { + removeDir(index_dir); + } else { + invalidateIndex(index_dir); + } } } } @@ -99,14 +110,14 @@ void removeInvalid(const string &base_dir, const vector<string> &indexes) { void DiskIndexCleaner::clean(const string &base_dir, const ActiveDiskIndexes &active_indexes) { vector<string> indexes = readIndexes(base_dir); - removeOld(base_dir, indexes, active_indexes); + removeOld(base_dir, indexes, active_indexes, false); removeInvalid(base_dir, indexes); } void DiskIndexCleaner::removeOldIndexes( const string &base_dir, const ActiveDiskIndexes &active_indexes) { vector<string> indexes = readIndexes(base_dir); - removeOld(base_dir, indexes, active_indexes); + removeOld(base_dir, indexes, active_indexes, true); } } diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp index a18d24931cb..1e63741084d 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp +++ b/searchcorespi/src/vespa/searchcorespi/index/indexmaintainer.cpp @@ -16,6 +16,7 @@ #include <vespa/vespalib/util/lambdatask.h> #include <sstream> #include <vespa/searchcorespi/flush/closureflushtask.h> +#include <vespa/vespalib/io/fileutil.h> #include <vespa/vespalib/util/exceptions.h> #include <vespa/vespalib/util/array.hpp> #include <vespa/fastos/file.h> @@ -999,6 +1000,7 @@ IndexMaintainer::runFusion(const FusionSpec &fusion_spec) _activeFusionSchema.reset(); _activeFusionPrunedSchema.reset(); } + vespalib::File::sync(vespalib::dirname(fail_dir)); return fusion_spec.last_fusion_id; } diff --git a/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp b/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp index c8b2e81a9c0..ebb316610e1 100644 --- a/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp +++ b/searchcorespi/src/vespa/searchcorespi/index/indexwriteutilities.cpp @@ -5,6 +5,7 @@ #include <vespa/searchlib/common/serialnumfileheadercontext.h> #include <vespa/searchlib/index/schemautil.h> #include <vespa/fastlib/io/bufferedfile.h> +#include <vespa/vespalib/io/fileutil.h> #include <vespa/vespalib/util/exceptions.h> #include <sstream> #include <unistd.h> @@ -57,6 +58,7 @@ IndexWriteUtilities::writeSerialNum(SerialNum serialNum, tmpFileName.c_str()); } file.Close(); + vespalib::File::sync(dir); if (ok) { FastOS_File renameFile(tmpFileName.c_str()); @@ -67,6 +69,7 @@ IndexWriteUtilities::writeSerialNum(SerialNum serialNum, msg << "Unable to write serial number to '" << dir << "'."; throw IllegalStateException(msg.str()); } + vespalib::File::sync(dir); } bool @@ -94,12 +97,14 @@ IndexWriteUtilities::copySerialNumFile(const vespalib::string &sourceDir, return false; } file.Close(); + vespalib::File::sync(destDir); if (!file.Rename(dest.c_str())) { LOG(error, "Unable to rename file '%s' to '%s'", tmpDest.c_str(), dest.c_str()); return false; } + vespalib::File::sync(destDir); return true; } @@ -151,6 +156,7 @@ IndexWriteUtilities::updateDiskIndexSchema(const vespalib::string &indexDir, } vespalib::string schemaTmpName = schemaName + ".tmp"; vespalib::string schemaOrigName = schemaName + ".orig"; + vespalib::unlink(schemaTmpName); if (!newSchema->saveToFile(schemaTmpName)) { LOG(error, "Could not save schema to '%s'", schemaTmpName.c_str()); @@ -172,6 +178,7 @@ IndexWriteUtilities::updateDiskIndexSchema(const vespalib::string &indexDir, schemaName.c_str(), FastOS_File::getLastErrorString().c_str()); } + vespalib::File::sync(indexDir); } // XXX: FastOS layer violation int renameres = ::rename(schemaTmpName.c_str(), schemaName.c_str()); @@ -183,6 +190,7 @@ IndexWriteUtilities::updateDiskIndexSchema(const vespalib::string &indexDir, schemaName.c_str(), errString.c_str()); } + vespalib::File::sync(indexDir); } } // namespace index diff --git a/searchlib/src/vespa/searchlib/common/indexmetainfo.cpp b/searchlib/src/vespa/searchlib/common/indexmetainfo.cpp index 10015bb658a..837c38eb340 100644 --- a/searchlib/src/vespa/searchlib/common/indexmetainfo.cpp +++ b/searchlib/src/vespa/searchlib/common/indexmetainfo.cpp @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "indexmetainfo.h" +#include <vespa/vespalib/io/fileutil.h> #include <vespa/vespalib/util/stringfmt.h> #include <vespa/vespalib/util/guard.h> #include <cassert> @@ -310,6 +311,7 @@ IndexMetaInfo::save(const vespalib::string &baseName) { vespalib::string fileName = makeFileName(baseName); vespalib::string newName = fileName + ".new"; + vespalib::unlink(newName); vespalib::FilePointer f(fopen(newName.c_str(), "w")); if (!f.valid()) { LOG(warning, "could not open file for writing: %s", newName.c_str()); @@ -350,6 +352,7 @@ IndexMetaInfo::save(const vespalib::string &baseName) newName.c_str(), fileName.c_str()); return false; } + vespalib::File::sync(vespalib::dirname(fileName)); return true; } diff --git a/searchlib/src/vespa/searchlib/diskindex/fusion.cpp b/searchlib/src/vespa/searchlib/diskindex/fusion.cpp index e61fe7bab17..2db6025d257 100644 --- a/searchlib/src/vespa/searchlib/diskindex/fusion.cpp +++ b/searchlib/src/vespa/searchlib/diskindex/fusion.cpp @@ -218,6 +218,7 @@ Fusion::mergeField(uint32_t id) } if (!FileKit::createStamp(indexDir + "/.mergeocc_done")) return false; + vespalib::File::sync(indexDir); if (!CleanTmpDirs()) return false; diff --git a/searchlib/src/vespa/searchlib/diskindex/indexbuilder.cpp b/searchlib/src/vespa/searchlib/diskindex/indexbuilder.cpp index b879e1f65a6..b744a68932d 100644 --- a/searchlib/src/vespa/searchlib/diskindex/indexbuilder.cpp +++ b/searchlib/src/vespa/searchlib/diskindex/indexbuilder.cpp @@ -700,6 +700,7 @@ IndexBuilder::close() for (FieldHandle & fh : _fields) { if (fh.getValid()) { fh.close(); + vespalib::File::sync(fh.getDir()); } } if (!docsummary::DocumentSummary::writeDocIdLimit(_prefix, _docIdLimit)) { diff --git a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp index 88c2dd9ecc3..1caff132779 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp +++ b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp @@ -3,6 +3,7 @@ #include "domain.h" #include <vespa/vespalib/util/stringfmt.h> #include <vespa/vespalib/util/closuretask.h> +#include <vespa/vespalib/io/fileutil.h> #include <vespa/fastos/file.h> #include <algorithm> #include <thread> @@ -60,6 +61,7 @@ Domain::Domain(const string &domainName, const string & baseDir, Executor & comm _sessionExecutor.sync(); if (_parts.empty() || _parts.crbegin()->second->isClosed()) { _parts[lastPart].reset(new DomainPart(_name, dir(), lastPart, _defaultCrcType, _fileHeaderContext, false)); + vespalib::File::sync(dir()); } } @@ -294,6 +296,7 @@ void Domain::commit(const Packet & packet) _parts[entry.serial()] = dp; } dp = _parts.rbegin()->second; + vespalib::File::sync(dir()); } dp->commit(entry.serial(), packet); cleanSessions(); @@ -310,6 +313,7 @@ bool Domain::erase(SerialNum to) _parts.erase(it); } retval = retval && dp->erase(to); + vespalib::File::sync(dir()); } if (_parts.begin()->second->range().to() >= to) { _parts.begin()->second->erase(to); diff --git a/searchlib/src/vespa/searchlib/transactionlog/translogserver.cpp b/searchlib/src/vespa/searchlib/transactionlog/translogserver.cpp index 4c3c5609a93..bfca137ba06 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/translogserver.cpp +++ b/searchlib/src/vespa/searchlib/transactionlog/translogserver.cpp @@ -336,6 +336,29 @@ void TransLogServer::exportRPC(FRT_Supervisor & supervisor) rb.ReturnDesc("syncedto", "Entry synced to"); } +namespace { + +void +writeDomainDir(std::lock_guard<std::mutex> &guard, + vespalib::string dir, + vespalib::string domainList, + std::map<vespalib::string, std::shared_ptr<Domain>> &domains) +{ + (void) guard; + vespalib::string domainListTmp(domainList + ".tmp"); + vespalib::unlink(domainListTmp); + std::ofstream domainDir(domainListTmp.c_str(), std::ios::trunc); + for (const auto &domainEntry : domains) { + domainDir << domainEntry.first << std::endl; + } + domainDir.close(); + vespalib::File::sync(domainListTmp); + vespalib::rename(domainListTmp, domainList, false, false); + vespalib::File::sync(dir); +} + +} + void TransLogServer::createDomain(FRT_RPCRequest *req) { uint32_t retval(0); @@ -351,12 +374,9 @@ void TransLogServer::createDomain(FRT_RPCRequest *req) try { domain = std::make_shared<Domain>(domainName, dir(), _commitExecutor, _sessionExecutor, _domainPartSize, _defaultCrcType, _fileHeaderContext); - { - Guard domainGuard(_lock); - _domains[domain->name()] = domain; - } - std::ofstream domainDir(domainList().c_str(), std::ios::app); - domainDir << domain->name() << std::endl; + Guard domainGuard(_lock); + _domains[domain->name()] = domain; + writeDomainDir(domainGuard, dir(), domainList(), _domains); } catch (const std::exception & e) { LOG(warning, "Failed creating %s domain. Exception = %s", domainName, e.what()); retval = uint32_t(-1); @@ -385,12 +405,10 @@ void TransLogServer::deleteDomain(FRT_RPCRequest *req) Guard domainGuard(_lock); _domains.erase(domainName); } - vespalib::rmdir(Domain::getDir(dir(), domainName).c_str(), true); - std::ofstream domainDir(domainList().c_str(), std::ios::trunc); + vespalib::rmdir(Domain::getDir(dir(), domainName), true); + vespalib::File::sync(dir()); Guard domainGuard(_lock); - for (DomainList::const_iterator it(_domains.begin()), mt(_domains.end()); it != mt; it++) { - domainDir << it->first << std::endl; - } + writeDomainDir(domainGuard, dir(), domainList(), _domains); } catch (const std::exception & e) { msg = make_string("Failed deleting %s domain. Exception = %s", domainName, e.what()); retval = -1; diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java index 3bfe492a125..a01bbe2fae1 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java @@ -5,14 +5,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.yahoo.vespa.athenz.api.AthenzService; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.IdentityDocumentEntity; import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.VespaUniqueInstanceIdEntity; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; -import java.util.Base64; import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.fromDottedString; @@ -35,29 +32,12 @@ public class EntityBindingsMapper { } } - public static VespaUniqueInstanceId toVespaUniqueInstanceId(VespaUniqueInstanceIdEntity entity) { - return new VespaUniqueInstanceId( - entity.clusterIndex, entity.clusterId, entity.instance, entity.application, entity.tenant, entity.region, entity.environment, entity.type != null ? IdentityType.fromId(entity.type) : null); // TODO Remove support for legacy representation without type - } - - public static IdentityDocument toIdentityDocument(IdentityDocumentEntity entity) { - return new IdentityDocument( - toVespaUniqueInstanceId(entity.providerUniqueId), - entity.configServerHostname, - entity.instanceHostname, - entity.createdAt, - entity.ipAddresses); - } - public static SignedIdentityDocument toSignedIdentityDocument(SignedIdentityDocumentEntity entity) { return new SignedIdentityDocument( - entity.identityDocument != null ? toIdentityDocument(entity.identityDocument) : null, entity.signature, entity.signingKeyVersion, fromDottedString(entity.providerUniqueId), - entity.dnsSuffix, new AthenzService(entity.providerService), - entity.ztsEndpoint, entity.documentVersion, entity.configServerHostname, entity.instanceHostname, @@ -66,42 +46,18 @@ public class EntityBindingsMapper { entity.identityType != null ? IdentityType.fromId(entity.identityType) : null); // TODO Remove support for legacy representation without type } - public static VespaUniqueInstanceIdEntity toVespaUniqueInstanceIdEntity(VespaUniqueInstanceId model) { - return new VespaUniqueInstanceIdEntity( - model.tenant(), model.application(), model.environment(), model.region(), - model.instance(), model.clusterId(), model.clusterIndex(), model.type() != null ? model.type().id() : null); // TODO Remove support for legacy representation without type - } - - public static IdentityDocumentEntity toIdentityDocumentEntity(IdentityDocument model) { - return new IdentityDocumentEntity( - toVespaUniqueInstanceIdEntity(model.providerUniqueId()), + public static SignedIdentityDocumentEntity toSignedIdentityDocumentEntity(SignedIdentityDocument model) { + return new SignedIdentityDocumentEntity( + model.signature(), + model.signingKeyVersion(), + model.providerUniqueId().asDottedString(), + model.providerService().getFullName(), + model.documentVersion(), model.configServerHostname(), model.instanceHostname(), model.createdAt(), - model.ipAddresses()); - } - - public static SignedIdentityDocumentEntity toSignedIdentityDocumentEntity(SignedIdentityDocument model) { - try { - IdentityDocumentEntity identityDocumentEntity = model.identityDocument() != null ? toIdentityDocumentEntity(model.identityDocument()) : null; - String rawDocument = Base64.getEncoder().encodeToString(mapper.writeValueAsString(identityDocumentEntity).getBytes()); - return new SignedIdentityDocumentEntity( - rawDocument, - model.signature(), - model.signingKeyVersion(), - model.providerUniqueId().asDottedString(), - model.dnsSuffix(), - model.providerService().getFullName(), - model.ztsEndpoint(), - model.documentVersion(), - model.configServerHostname(), - model.instanceHostname(), - model.createdAt(), - model.ipAddresses(), - model.identityType() != null ? model.identityType().id() : null); // TODO Remove support for legacy representation without type - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } + model.ipAddresses(), + model.identityType() != null ? model.identityType().id() : null); // TODO Remove support for legacy representation without type } public static SignedIdentityDocument readSignedIdentityDocumentFromFile(Path file) { diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java deleted file mode 100644 index 82d0a3d622c..00000000000 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.athenz.identityprovider.api; - -import java.time.Instant; -import java.util.Set; - -/** - * The identity document that contains the instance specific information - * - * @author bjorncs - * @deprecated Will soon be inlined into {@link SignedIdentityDocument} - */ -@Deprecated -public class IdentityDocument { - private final VespaUniqueInstanceId providerUniqueId; - private final String configServerHostname; - private final String instanceHostname; - private final Instant createdAt; - private final Set<String> ipAddresses; - - public IdentityDocument(VespaUniqueInstanceId providerUniqueId, - String configServerHostname, - String instanceHostname, - Instant createdAt, - Set<String> ipAddresses) { - this.providerUniqueId = providerUniqueId; - this.configServerHostname = configServerHostname; - this.instanceHostname = instanceHostname; - this.createdAt = createdAt; - this.ipAddresses = ipAddresses; - } - - public VespaUniqueInstanceId providerUniqueId() { - return providerUniqueId; - } - - public String configServerHostname() { - return configServerHostname; - } - - public String instanceHostname() { - return instanceHostname; - } - - public Instant createdAt() { - return createdAt; - } - - public Set<String> ipAddresses() { - return ipAddresses; - } -} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java index aa1dbd4dac3..dc5dae9d516 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java @@ -3,12 +3,11 @@ package com.yahoo.vespa.athenz.identityprovider.api; import com.yahoo.vespa.athenz.api.AthenzService; -import java.net.URI; import java.time.Instant; import java.util.Set; /** - * A signed identity document which contains a {@link IdentityDocument} + * A signed identity document * * @author bjorncs */ @@ -16,13 +15,10 @@ public class SignedIdentityDocument { public static final int DEFAULT_KEY_VERSION = 0; public static final int DEFAULT_DOCUMENT_VERSION = 1; - private final IdentityDocument identityDocument; private final String signature; private final int signingKeyVersion; private final VespaUniqueInstanceId providerUniqueId; - private final String dnsSuffix; private final AthenzService providerService; - private final URI ztsEndpoint; private final int documentVersion; private final String configServerHostname; private final String instanceHostname; @@ -30,26 +26,20 @@ public class SignedIdentityDocument { private final Set<String> ipAddresses; private final IdentityType identityType; - public SignedIdentityDocument(IdentityDocument identityDocument, - String signature, + public SignedIdentityDocument(String signature, int signingKeyVersion, VespaUniqueInstanceId providerUniqueId, - String dnsSuffix, AthenzService providerService, - URI ztsEndpoint, int documentVersion, String configServerHostname, String instanceHostname, Instant createdAt, Set<String> ipAddresses, IdentityType identityType) { - this.identityDocument = identityDocument; this.signature = signature; this.signingKeyVersion = signingKeyVersion; this.providerUniqueId = providerUniqueId; - this.dnsSuffix = dnsSuffix; this.providerService = providerService; - this.ztsEndpoint = ztsEndpoint; this.documentVersion = documentVersion; this.configServerHostname = configServerHostname; this.instanceHostname = instanceHostname; @@ -58,11 +48,6 @@ public class SignedIdentityDocument { this.identityType = identityType; } - @Deprecated - public IdentityDocument identityDocument() { - return identityDocument; - } - public String signature() { return signature; } @@ -75,20 +60,10 @@ public class SignedIdentityDocument { return providerUniqueId; } - @Deprecated - public String dnsSuffix() { - return dnsSuffix; - } - public AthenzService providerService() { return providerService; } - @Deprecated - public URI ztsEndpoint() { - return ztsEndpoint; - } - public int documentVersion() { return documentVersion; } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java deleted file mode 100644 index b4b2e82ab0e..00000000000 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.athenz.identityprovider.api.bindings; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.time.Instant; -import java.util.Objects; -import java.util.Set; - -/** - * @author bjorncs - * @deprecated Will soon be inlined into {@link SignedIdentityDocumentEntity} - */ -@JsonIgnoreProperties(ignoreUnknown = true) -@Deprecated -public class IdentityDocumentEntity { - - @JsonProperty("provider-unique-id") - public final VespaUniqueInstanceIdEntity providerUniqueId; - @JsonProperty("configserver-hostname") - public final String configServerHostname; - @JsonProperty("instance-hostname") - public final String instanceHostname; - @JsonProperty("created-at") - public final Instant createdAt; - @JsonProperty("ip-addresses") - public final Set<String> ipAddresses; - - public IdentityDocumentEntity( - @JsonProperty("provider-unique-id") VespaUniqueInstanceIdEntity providerUniqueId, - @JsonProperty("configserver-hostname") String configServerHostname, - @JsonProperty("instance-hostname") String instanceHostname, - @JsonProperty("created-at") Instant createdAt, - @JsonProperty("ip-addresses") Set<String> ipAddresses) { - this.providerUniqueId = providerUniqueId; - this.configServerHostname = configServerHostname; - this.instanceHostname = instanceHostname; - this.createdAt = createdAt; - this.ipAddresses = ipAddresses; - } - - - @Override - public String toString() { - return "IdentityDocumentEntity{" + - "providerUniqueId=" + providerUniqueId + - ", configServerHostname='" + configServerHostname + '\'' + - ", instanceHostname='" + instanceHostname + '\'' + - ", createdAt=" + createdAt + - ", ipAddresses=" + ipAddresses + - '}'; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - IdentityDocumentEntity that = (IdentityDocumentEntity) o; - return Objects.equals(providerUniqueId, that.providerUniqueId) && - Objects.equals(configServerHostname, that.configServerHostname) && - Objects.equals(instanceHostname, that.instanceHostname) && - Objects.equals(createdAt, that.createdAt) && - Objects.equals(ipAddresses, that.ipAddresses); - } - - @Override - public int hashCode() { - - return Objects.hash(providerUniqueId, configServerHostname, instanceHostname, createdAt, ipAddresses); - } -} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java index 3d4872549d6..52d33f79c1d 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java @@ -2,17 +2,10 @@ package com.yahoo.vespa.athenz.identityprovider.api.bindings; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URI; import java.time.Instant; -import java.util.Base64; import java.util.Objects; import java.util.Set; @@ -22,16 +15,10 @@ import java.util.Set; @JsonIgnoreProperties(ignoreUnknown = true) public class SignedIdentityDocumentEntity { - private static final ObjectMapper mapper = createObjectMapper(); - - @JsonProperty("identity-document")public final String rawIdentityDocument; - @JsonIgnore @Deprecated public final IdentityDocumentEntity identityDocument; @JsonProperty("signature") public final String signature; @JsonProperty("signing-key-version") public final int signingKeyVersion; @JsonProperty("provider-unique-id") public final String providerUniqueId; // String representation - @JsonProperty("dns-suffix") public final String dnsSuffix; @JsonProperty("provider-service") public final String providerService; - @JsonProperty("zts-endpoint") public final URI ztsEndpoint; @JsonProperty("document-version") public final int documentVersion; @JsonProperty("configserver-hostname") public final String configServerHostname; @JsonProperty("instance-hostname") public final String instanceHostname; @@ -40,27 +27,20 @@ public class SignedIdentityDocumentEntity { @JsonProperty("identity-type") public final String identityType; @JsonCreator - public SignedIdentityDocumentEntity(@JsonProperty("identity-document") String rawIdentityDocument, - @JsonProperty("signature") String signature, + public SignedIdentityDocumentEntity(@JsonProperty("signature") String signature, @JsonProperty("signing-key-version") int signingKeyVersion, @JsonProperty("provider-unique-id") String providerUniqueId, - @JsonProperty("dns-suffix") String dnsSuffix, @JsonProperty("provider-service") String providerService, - @JsonProperty("zts-endpoint") URI ztsEndpoint, @JsonProperty("document-version") int documentVersion, @JsonProperty("configserver-hostname") String configServerHostname, @JsonProperty("instance-hostname") String instanceHostname, @JsonProperty("created-at") Instant createdAt, @JsonProperty("ip-addresses") Set<String> ipAddresses, @JsonProperty("identity-type") String identityType) { - this.rawIdentityDocument = rawIdentityDocument; - this.identityDocument = parseIdentityDocument(rawIdentityDocument); this.signature = signature; this.signingKeyVersion = signingKeyVersion; this.providerUniqueId = providerUniqueId; - this.dnsSuffix = dnsSuffix; this.providerService = providerService; - this.ztsEndpoint = ztsEndpoint; this.documentVersion = documentVersion; this.configServerHostname = configServerHostname; this.instanceHostname = instanceHostname; @@ -69,31 +49,13 @@ public class SignedIdentityDocumentEntity { this.identityType = identityType; } - private static IdentityDocumentEntity parseIdentityDocument(String rawIdentityDocument) { - try { - return mapper.readValue(Base64.getDecoder().decode(rawIdentityDocument), IdentityDocumentEntity.class); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static ObjectMapper createObjectMapper() { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new JavaTimeModule()); - return mapper; - } - @Override public String toString() { return "SignedIdentityDocumentEntity{" + - "rawIdentityDocument='" + rawIdentityDocument + '\'' + - ", identityDocument=" + identityDocument + ", signature='" + signature + '\'' + ", signingKeyVersion=" + signingKeyVersion + ", providerUniqueId='" + providerUniqueId + '\'' + - ", dnsSuffix='" + dnsSuffix + '\'' + ", providerService='" + providerService + '\'' + - ", ztsEndpoint=" + ztsEndpoint + ", documentVersion=" + documentVersion + ", configServerHostname='" + configServerHostname + '\'' + ", instanceHostname='" + instanceHostname + '\'' + @@ -110,13 +72,9 @@ public class SignedIdentityDocumentEntity { SignedIdentityDocumentEntity that = (SignedIdentityDocumentEntity) o; return signingKeyVersion == that.signingKeyVersion && documentVersion == that.documentVersion && - Objects.equals(rawIdentityDocument, that.rawIdentityDocument) && - Objects.equals(identityDocument, that.identityDocument) && Objects.equals(signature, that.signature) && Objects.equals(providerUniqueId, that.providerUniqueId) && - Objects.equals(dnsSuffix, that.dnsSuffix) && Objects.equals(providerService, that.providerService) && - Objects.equals(ztsEndpoint, that.ztsEndpoint) && Objects.equals(configServerHostname, that.configServerHostname) && Objects.equals(instanceHostname, that.instanceHostname) && Objects.equals(createdAt, that.createdAt) && @@ -126,6 +84,6 @@ public class SignedIdentityDocumentEntity { @Override public int hashCode() { - return Objects.hash(rawIdentityDocument, identityDocument, signature, signingKeyVersion, providerUniqueId, dnsSuffix, providerService, ztsEndpoint, documentVersion, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType); + return Objects.hash(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType); } } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java deleted file mode 100644 index 3fdbb49b28e..00000000000 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/VespaUniqueInstanceIdEntity.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.athenz.identityprovider.api.bindings; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Objects; - -/** - * @author bjorncs - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class VespaUniqueInstanceIdEntity { - - @JsonProperty("tenant") - public final String tenant; - @JsonProperty("application") - public final String application; - @JsonProperty("environment") - public final String environment; - @JsonProperty("region") - public final String region; - @JsonProperty("instance") - public final String instance; - @JsonProperty("cluster-id") - public final String clusterId; - @JsonProperty("cluster-index") - public final int clusterIndex; - @JsonProperty("type") - public final String type; - - @JsonCreator - public VespaUniqueInstanceIdEntity(@JsonProperty("tenant") String tenant, - @JsonProperty("application") String application, - @JsonProperty("environment") String environment, - @JsonProperty("region") String region, - @JsonProperty("instance") String instance, - @JsonProperty("cluster-id") String clusterId, - @JsonProperty("cluster-index") int clusterIndex, - @JsonProperty("type") String type) { - this.tenant = tenant; - this.application = application; - this.environment = environment; - this.region = region; - this.instance = instance; - this.clusterId = clusterId; - this.clusterIndex = clusterIndex; - this.type = type; - } - - @Deprecated - public VespaUniqueInstanceIdEntity(String tenant, - String application, - String environment, - String region, - String instance, - String clusterId, - int clusterIndex) { - this(tenant, application, environment, region, instance, clusterId, clusterIndex, null); - } - - - @Override - public String toString() { - return "VespaUniqueInstanceIdEntity{" + - "tenant='" + tenant + '\'' + - ", application='" + application + '\'' + - ", environment='" + environment + '\'' + - ", region='" + region + '\'' + - ", instance='" + instance + '\'' + - ", clusterId='" + clusterId + '\'' + - ", clusterIndex=" + clusterIndex + - ", type='" + type + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - VespaUniqueInstanceIdEntity that = (VespaUniqueInstanceIdEntity) o; - return clusterIndex == that.clusterIndex && - Objects.equals(tenant, that.tenant) && - Objects.equals(application, that.application) && - Objects.equals(environment, that.environment) && - Objects.equals(region, that.region) && - Objects.equals(instance, that.instance) && - Objects.equals(clusterId, that.clusterId) && - Objects.equals(type, that.type); - } - - @Override - public int hashCode() { - return Objects.hash(tenant, application, environment, region, instance, clusterId, clusterIndex, type); - } -} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java index 55e9103b040..05459e5488b 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/utils/SiaUtils.java @@ -7,13 +7,18 @@ import com.yahoo.vespa.athenz.tls.X509CertificateUtils; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.List; import java.util.Optional; +import java.util.stream.StreamSupport; + +import static java.util.stream.Collectors.toList; /** * Misc utility methods for SIA provided credentials @@ -105,6 +110,25 @@ public class SiaUtils { } } + public static List<AthenzService> findSiaServices() { + return findSiaServices(DEFAULT_SIA_DIRECTORY); + } + + public static List<AthenzService> findSiaServices(Path root) { + String keyFileSuffix = ".key.pem"; + Path keysDirectory = root.resolve("keys"); + try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(keysDirectory)) { + return StreamSupport.stream(directoryStream.spliterator(), false) + .map(path -> path.getFileName().toString()) + .filter(fileName -> fileName.endsWith(keyFileSuffix)) + .map(fileName -> fileName.substring(0, fileName.length() - keyFileSuffix.length())) + .map(AthenzService::new) + .collect(toList()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + private static Path toTempFile(Path file) { return Paths.get(file.toAbsolutePath().toString() + ".tmp"); } diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java index 9cc500ee241..38483bdbaee 100644 --- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java @@ -9,7 +9,6 @@ import com.yahoo.vespa.athenz.tls.KeyAlgorithm; import com.yahoo.vespa.athenz.tls.KeyUtils; import org.junit.Test; -import java.net.URI; import java.security.KeyPair; import java.time.Instant; import java.util.Arrays; @@ -18,7 +17,7 @@ import java.util.HashSet; import static com.yahoo.vespa.athenz.identityprovider.api.IdentityType.TENANT; import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION; import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.DEFAULT_KEY_VERSION; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; /** * @author bjorncs @@ -41,7 +40,7 @@ public class IdentityDocumentSignerTest { signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt, ipAddresses, identityType, keyPair.getPrivate()); SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument( - null, signature, DEFAULT_KEY_VERSION, id, "dns-suffix", providerService, URI.create("https://zts"), + signature, DEFAULT_KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname, instanceHostname, createdAt, ipAddresses, identityType); assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic())); diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java new file mode 100644 index 00000000000..0282373cdaf --- /dev/null +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/utils/SiaUtilsTest.java @@ -0,0 +1,40 @@ +package com.yahoo.vespa.athenz.utils; + +import com.yahoo.vespa.athenz.api.AthenzService; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.junit.Assert.assertThat; + +/** + * @author bjorncs + */ +public class SiaUtilsTest { + + @Rule + public TemporaryFolder tempDirectory = new TemporaryFolder(); + + @Test + public void it_finds_all_identity_names_from_files_in_sia_keys_directory() throws IOException { + Path siaRoot = tempDirectory.getRoot().toPath(); + Files.createDirectory(siaRoot.resolve("keys")); + AthenzService fooService = new AthenzService("my.domain.foo"); + Files.createFile(SiaUtils.getPrivateKeyFile(siaRoot, fooService)); + AthenzService barService = new AthenzService("my.domain.bar"); + Files.createFile(SiaUtils.getPrivateKeyFile(siaRoot, barService)); + + List<AthenzService> siaIdentities = SiaUtils.findSiaServices(siaRoot); + assertThat(siaIdentities.size(), equalTo(2)); + assertThat(siaIdentities, hasItem(fooService)); + assertThat(siaIdentities, hasItem(barService)); + } + +}
\ No newline at end of file diff --git a/vespabase/src/common-env.sh b/vespabase/src/common-env.sh index 8dfcf8d2c4c..018630b0622 100755 --- a/vespabase/src/common-env.sh +++ b/vespabase/src/common-env.sh @@ -107,6 +107,13 @@ populate_environment () { fi } +add_valgrind_suppressions_file() { + if [ -f "$1" ] + then + VESPA_VALGRIND_SUPPREESSIONS_OPT="$VESPA_VALGRIND_SUPPREESSIONS_OPT --suppressions=$1" + fi +} + populate_environment PATH=$VESPA_HOME/bin64:$VESPA_HOME/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/bin:/usr/sbin:/usr/bin @@ -119,13 +126,17 @@ if [ "$JAVA_HOME" ] && [ -f "${JAVA_HOME}/bin/java" ]; then PATH="${PATH}:${JAVA_HOME}/bin" fi +VESPA_VALGRIND_SUPPREESSIONS_OPT="" +add_valgrind_suppressions_file ${VESPA_HOME}/etc/vespa/valgrind-suppressions.txt +add_valgrind_suppressions_file ${VESPA_HOME}/etc/vespa/suppressions.txt + consider_fallback VESPA_VALGRIND_OPT "--num-callers=32 \ --run-libc-freeres=yes \ --track-origins=yes \ --freelist-vol=1000000000 \ --leak-check=full \ --show-reachable=yes \ ---suppressions=${VESPA_HOME}/etc/vespa/suppressions.txt" +${VESPA_VALGRIND_SUPPREESSIONS_OPT}" consider_fallback VESPA_USE_HUGEPAGES_LIST $(get_var "hugepages_list") consider_fallback VESPA_USE_HUGEPAGES_LIST "all" diff --git a/vespalib/src/vespa/vespalib/io/fileutil.cpp b/vespalib/src/vespa/vespalib/io/fileutil.cpp index e360c84f569..5ab5fb99a0d 100644 --- a/vespalib/src/vespa/vespalib/io/fileutil.cpp +++ b/vespalib/src/vespa/vespalib/io/fileutil.cpp @@ -408,6 +408,15 @@ File::sync() } } +void +File::sync(vespalib::stringref path) +{ + File file(path); + file.open(READONLY); + file.sync(); + file.close(); +} + bool File::close() { diff --git a/vespalib/src/vespa/vespalib/io/fileutil.h b/vespalib/src/vespa/vespalib/io/fileutil.h index 187939ed412..1b493a9ebf1 100644 --- a/vespalib/src/vespa/vespalib/io/fileutil.h +++ b/vespalib/src/vespa/vespalib/io/fileutil.h @@ -190,6 +190,16 @@ public: */ static vespalib::string readAll(vespalib::stringref path); + /** + * Sync file or directory. + * + * This is a convenience function for the member functions open() and + * sync(), see there for more details. + * + * @throw IoException If we failed to sync the file. + */ + static void sync(vespalib::stringref path); + virtual void sync(); virtual bool close(); virtual bool unlink(); |