diff options
Diffstat (limited to 'configserver/src/main/java/com/yahoo')
15 files changed, 336 insertions, 216 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index 3502ece9cb7..c7e4022c668 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -167,7 +167,6 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye ConfigserverConfig configserverConfig, Orchestrator orchestrator, TesterClient testerClient, - Zone zone, HealthCheckerProvider healthCheckers, Metric metric, SecretStore secretStore, @@ -698,10 +697,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Optional<String> applicationPackage = Optional.empty(); Optional<Session> session = getActiveSession(applicationId); if (session.isPresent()) { - FileReference applicationPackageReference = session.get().getApplicationPackageReference(); + Optional<FileReference> applicationPackageReference = session.get().getApplicationPackageReference(); File downloadDirectory = new File(Defaults.getDefaults().underVespaHome(configserverConfig().fileReferencesDir())); - if (applicationPackageReference != null && ! fileReferenceExistsOnDisk(downloadDirectory, applicationPackageReference)) - applicationPackage = Optional.of(applicationPackageReference.value()); + if (applicationPackageReference.isPresent() && ! fileReferenceExistsOnDisk(downloadDirectory, applicationPackageReference.get())) + applicationPackage = Optional.of(applicationPackageReference.get().value()); } return applicationPackage; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java index 0acf32d79a7..efa62625159 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java @@ -96,8 +96,8 @@ public class ZooKeeperClient { Path zkPath = getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(SCHEMAS_DIR); curator.create(zkPath); // Ensures that ranking expressions and other files are also written - writeDir(app.getFile(ApplicationPackage.SEARCH_DEFINITIONS_DIR), zkPath, true); - writeDir(app.getFile(ApplicationPackage.SCHEMAS_DIR), zkPath, true); + writeDir(app.getFile(ApplicationPackage.SEARCH_DEFINITIONS_DIR), zkPath); + writeDir(app.getFile(ApplicationPackage.SCHEMAS_DIR), zkPath); for (NamedReader sd : schemas) { curator.set(zkPath.append(sd.getName()), Utf8.toBytes(com.yahoo.io.IOUtils.readAll(sd.getReader()))); sd.getReader().close(); @@ -105,7 +105,7 @@ public class ZooKeeperClient { } /** - * Puts some of the application package files into ZK - see write(app). + * Writes some application package files into ZK - see write(app). * * @param app the application package to use as input. * @throws java.io.IOException if not able to write to Zookeeper @@ -118,45 +118,40 @@ public class ZooKeeperClient { writeFile(app.getFile(Path.fromString(VALIDATION_OVERRIDES.getName())), getZooKeeperAppPath(USERAPP_ZK_SUBPATH)); writeDir(app.getFile(RULES_DIR), getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(RULES_DIR), - (path) -> path.getName().endsWith(ApplicationPackage.RULES_NAME_SUFFIX), - true); + (path) -> path.getName().endsWith(ApplicationPackage.RULES_NAME_SUFFIX)); writeDir(app.getFile(QUERY_PROFILES_DIR), getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(QUERY_PROFILES_DIR), - xmlFilter, true); + xmlFilter); writeDir(app.getFile(PAGE_TEMPLATES_DIR), getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(PAGE_TEMPLATES_DIR), - xmlFilter, true); + xmlFilter); writeDir(app.getFile(Path.fromString(SEARCHCHAINS_DIR)), getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(SEARCHCHAINS_DIR), - xmlFilter, true); + xmlFilter); writeDir(app.getFile(Path.fromString(DOCPROCCHAINS_DIR)), getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(DOCPROCCHAINS_DIR), - xmlFilter, true); + xmlFilter); writeDir(app.getFile(Path.fromString(ROUTINGTABLES_DIR)), getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(ROUTINGTABLES_DIR), - xmlFilter, true); + xmlFilter); writeDir(app.getFile(MODELS_GENERATED_REPLICATED_DIR), - getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(MODELS_GENERATED_REPLICATED_DIR), - true); + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(MODELS_GENERATED_REPLICATED_DIR)); writeDir(app.getFile(SECURITY_DIR), - getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(SECURITY_DIR), - true); + getZooKeeperAppPath(USERAPP_ZK_SUBPATH).append(SECURITY_DIR)); } - private void writeDir(ApplicationFile file, Path zooKeeperAppPath, boolean recurse) throws IOException { - writeDir(file, zooKeeperAppPath, (__) -> true, recurse); + private void writeDir(ApplicationFile file, Path zooKeeperAppPath) throws IOException { + writeDir(file, zooKeeperAppPath, (__) -> true); } - private void writeDir(ApplicationFile dir, Path path, ApplicationFile.PathFilter filenameFilter, boolean recurse) throws IOException { + private void writeDir(ApplicationFile dir, Path path, ApplicationFile.PathFilter filenameFilter) throws IOException { if ( ! dir.isDirectory()) return; for (ApplicationFile file : listFiles(dir, filenameFilter)) { String name = file.getPath().getName(); if (name.startsWith(".")) continue; //.svn , .git ... if (file.isDirectory()) { curator.create(path.append(name)); - if (recurse) { - writeDir(file, path.append(name), filenameFilter, recurse); - } + writeDir(file, path.append(name), filenameFilter); } else { writeFile(file, path); } @@ -202,9 +197,7 @@ public class ZooKeeperClient { if (files == null || files.isEmpty()) { curator.create(getZooKeeperAppPath(USERAPP_ZK_SUBPATH + "/" + userInclude)); } - writeDir(dir, - getZooKeeperAppPath(USERAPP_ZK_SUBPATH + "/" + userInclude), - xmlFilter, true); + writeDir(dir, getZooKeeperAppPath(USERAPP_ZK_SUBPATH + "/" + userInclude), xmlFilter); } } @@ -249,7 +242,7 @@ public class ZooKeeperClient { .forEach(path -> curator.delete(getZooKeeperAppPath(path))); } catch (Exception e) { logger.log(Level.WARNING, "Could not clean up in zookeeper: " + Exceptions.toMessageString(e)); - //Might be called in an exception handler before re-throw, so do not throw here. + // Might be called in an exception handler before re-throw, so do not throw here. } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java index da18c4e4fcc..6fe133958f5 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java @@ -24,12 +24,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.time.Clock; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import static com.yahoo.yolean.Exceptions.uncheck; +import static java.util.logging.Level.INFO; /** * Global file directory, holding files for file distribution for all deployed applications. @@ -40,7 +42,6 @@ public class FileDirectory extends AbstractComponent { private static final Logger log = Logger.getLogger(FileDirectory.class.getName()); private final Locks<FileReference> locks = new Locks<>(1, TimeUnit.MINUTES); - private final File root; @Inject @@ -67,7 +68,7 @@ public class FileDirectory extends AbstractComponent { } } - static private class Filter implements FilenameFilter { + private static class Filter implements FilenameFilter { @Override public boolean accept(File dir, String name) { return !".".equals(name) && !"..".equals(name) ; @@ -78,17 +79,23 @@ public class FileDirectory extends AbstractComponent { return root.getAbsolutePath() + "/" + ref.value(); } - public File getFile(FileReference reference) { + public Optional<File> getFile(FileReference reference) { ensureRootExist(); File dir = new File(getPath(reference)); - if (!dir.exists()) - throw new IllegalArgumentException("File reference '" + reference.value() + "' with absolute path '" + dir.getAbsolutePath() + "' does not exist."); - if (!dir.isDirectory()) - throw new IllegalArgumentException("File reference '" + reference.value() + "' with absolute path '" + dir.getAbsolutePath() + "' is not a directory."); - File [] files = dir.listFiles(new Filter()); - if (files == null || files.length == 0) - throw new IllegalArgumentException("File reference '" + reference.value() + "' with absolute path '" + dir.getAbsolutePath() + " does not contain any files"); - return files[0]; + if (!dir.exists()) { + log.log(INFO, "File reference '" + reference.value() + "' ('" + dir.getAbsolutePath() + "') does not exist."); + return Optional.empty(); + } + if (!dir.isDirectory()) { + log.log(INFO, "File reference '" + reference.value() + "' ('" + dir.getAbsolutePath() + ")' is not a directory."); + return Optional.empty(); + } + File[] files = dir.listFiles(new Filter()); + if (files == null || files.length == 0) { + log.log(INFO, "File reference '" + reference.value() + "' ('" + dir.getAbsolutePath() + "') does not contain any files"); + return Optional.empty(); + } + return Optional.of(files[0]); } public File getRoot() { return root; } @@ -136,7 +143,7 @@ public class FileDirectory extends AbstractComponent { private void deleteDirRecursively(File dir) { log.log(Level.FINE, "Will delete dir " + dir); if ( ! IOUtils.recursiveDeleteDir(dir)) - log.log(Level.INFO, "Failed to delete " + dir); + log.log(INFO, "Failed to delete " + dir); } // Check if we should add file, it might already exist diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java index 57d57d16d2f..e45c3a8e380 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java @@ -12,7 +12,6 @@ import com.yahoo.jrt.StringValue; import com.yahoo.jrt.Supervisor; import com.yahoo.jrt.Transport; import com.yahoo.vespa.config.ConnectionPool; -import com.yahoo.vespa.filedistribution.EmptyFileReferenceData; import com.yahoo.vespa.filedistribution.FileDistributionConnectionPool; import com.yahoo.vespa.filedistribution.FileDownloader; import com.yahoo.vespa.filedistribution.FileReferenceCompressor; @@ -20,14 +19,16 @@ import com.yahoo.vespa.filedistribution.FileReferenceData; import com.yahoo.vespa.filedistribution.FileReferenceDownload; import com.yahoo.vespa.filedistribution.LazyFileReferenceData; import com.yahoo.vespa.filedistribution.LazyTemporaryStorageFileReferenceData; -import com.yahoo.yolean.Exceptions; + import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -35,6 +36,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import static com.yahoo.vespa.config.server.filedistribution.FileDistributionUtil.getOtherConfigServersInCluster; +import static com.yahoo.vespa.config.server.filedistribution.FileServer.FileApiErrorCodes.NOT_FOUND; +import static com.yahoo.vespa.config.server.filedistribution.FileServer.FileApiErrorCodes.OK; +import static com.yahoo.vespa.config.server.filedistribution.FileServer.FileApiErrorCodes.TIMEOUT; +import static com.yahoo.vespa.config.server.filedistribution.FileServer.FileApiErrorCodes.TRANSFER_FAILED; import static com.yahoo.vespa.filedistribution.FileReferenceData.CompressionType; import static com.yahoo.vespa.filedistribution.FileReferenceData.CompressionType.gzip; import static com.yahoo.vespa.filedistribution.FileReferenceData.Type; @@ -54,10 +59,11 @@ public class FileServer { private final List<CompressionType> compressionTypes; // compression types to use, in preferred order // TODO: Move to filedistribution module, so that it can be used by both clients and servers - private enum FileApiErrorCodes { + enum FileApiErrorCodes { OK(0, "OK"), NOT_FOUND(1, "File reference not found"), - TIMEOUT(2, "Timeout"); + TIMEOUT(2, "Timeout"), + TRANSFER_FAILED(3, "Failed transferring file"); private final int code; private final String description; FileApiErrorCodes(int code, String description) { @@ -103,40 +109,33 @@ public class FileServer { } private boolean hasFile(FileReference reference) { - try { - return fileDirectory.getFile(reference).exists(); - } catch (IllegalArgumentException e) { - log.log(Level.FINE, () -> "Failed locating " + reference + ": " + e.getMessage()); - } + Optional<File> file = fileDirectory.getFile(reference); + if (file.isPresent()) + return file.get().exists(); + + log.log(Level.FINE, () -> "Failed locating " + reference); return false; } FileDirectory getRootDir() { return fileDirectory; } - void startFileServing(FileReference reference, Receiver target, Set<CompressionType> acceptedCompressionTypes) { - if ( ! fileDirectory.getFile(reference).exists()) return; - - File file = this.fileDirectory.getFile(reference); - log.log(Level.FINE, () -> "Start serving " + reference + " with file '" + file.getAbsolutePath() + "'"); - FileReferenceData fileData = EmptyFileReferenceData.empty(reference, file.getName()); - try { - fileData = readFileReferenceData(reference, acceptedCompressionTypes); + void startFileServing(FileReference reference, File file, Receiver target, Set<CompressionType> acceptedCompressionTypes) { + var absolutePath = file.getAbsolutePath(); + try (FileReferenceData fileData = fileReferenceData(reference, acceptedCompressionTypes, file)) { + log.log(Level.FINE, () -> "Start serving " + reference.value() + " with file '" + absolutePath + "'"); target.receive(fileData, new ReplayStatus(0, "OK")); - log.log(Level.FINE, () -> "Done serving " + reference.value() + " with file '" + file.getAbsolutePath() + "'"); - } catch (IOException e) { - String errorDescription = "For" + reference.value() + ": failed reading file '" + file.getAbsolutePath() + "'"; - log.warning(errorDescription + " for sending to '" + target.toString() + "'. " + e.getMessage()); - target.receive(fileData, new ReplayStatus(1, errorDescription)); + log.log(Level.FINE, () -> "Done serving " + reference.value() + " with file '" + absolutePath + "'"); + } catch (IOException ioe) { + throw new UncheckedIOException("For " + reference.value() + ": failed reading file '" + absolutePath + "'" + + " for sending to '" + target.toString() + "'. ", ioe); } catch (Exception e) { - log.log(Level.WARNING, "Failed serving " + reference + ": " + Exceptions.toMessageString(e)); - } finally { - fileData.close(); + throw new RuntimeException("Failed serving " + reference.value() + " to '" + target + "': ", e); } } - private FileReferenceData readFileReferenceData(FileReference reference, Set<CompressionType> acceptedCompressionTypes) throws IOException { - File file = this.fileDirectory.getFile(reference); - + private FileReferenceData fileReferenceData(FileReference reference, + Set<CompressionType> acceptedCompressionTypes, + File file) throws IOException { if (file.isDirectory()) { Path tempFile = Files.createTempFile("filereferencedata", reference.value()); CompressionType compressionType = chooseCompressionType(acceptedCompressionTypes); @@ -172,20 +171,21 @@ public class FileServer { Set<CompressionType> acceptedCompressionTypes) { if (Instant.now().isAfter(deadline)) { log.log(Level.INFO, () -> "Deadline exceeded for request for file reference '" + fileReference + "' from " + client); - return FileApiErrorCodes.TIMEOUT; + return TIMEOUT; } - boolean fileExists; try { var fileReferenceDownload = new FileReferenceDownload(fileReference, client, downloadFromOtherSourceIfNotFound); - fileExists = hasFileDownloadIfNeeded(fileReferenceDownload); - if (fileExists) startFileServing(fileReference, receiver, acceptedCompressionTypes); - } catch (IllegalArgumentException e) { - fileExists = false; + var file = getFileDownloadIfNeeded(fileReferenceDownload); + if (file.isEmpty()) return NOT_FOUND; + + startFileServing(fileReference, file.get(), receiver, acceptedCompressionTypes); + } catch (Exception e) { log.warning("Failed serving file reference '" + fileReference + "', request from " + client + " failed with: " + e.getMessage()); + return TRANSFER_FAILED; } - return (fileExists ? FileApiErrorCodes.OK : FileApiErrorCodes.NOT_FOUND); + return OK; } /* Choose the first compression type (list is in preferred order) that matches an accepted compression type, or fail */ @@ -198,9 +198,11 @@ public class FileServer { acceptedCompressionTypes + ", compression types server can use: " + compressionTypes); } - boolean hasFileDownloadIfNeeded(FileReferenceDownload fileReferenceDownload) { + public Optional<File> getFileDownloadIfNeeded(FileReferenceDownload fileReferenceDownload) { FileReference fileReference = fileReferenceDownload.fileReference(); - if (hasFile(fileReference)) return true; + Optional<File> file = fileDirectory.getFile(fileReference); + if (file.isPresent()) + return file; if (fileReferenceDownload.downloadFromOtherSourceIfNotFound()) { log.log(Level.FINE, "File not found, downloading from another source"); @@ -209,13 +211,13 @@ public class FileServer { FileReferenceDownload newDownload = new FileReferenceDownload(fileReference, fileReferenceDownload.client(), false); - boolean fileExists = downloader.getFile(newDownload).isPresent(); - if ( ! fileExists) + file = downloader.getFile(newDownload); + if (file.isEmpty()) log.log(Level.INFO, "Failed downloading '" + fileReferenceDownload + "'"); - return fileExists; + return file; } else { log.log(Level.FINE, "File not found, will not download from another source"); - return false; + return Optional.empty(); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java index 22ef6cc2547..031574bec77 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java @@ -66,15 +66,15 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer { Optional<Session> session = applicationRepository.getActiveSession(applicationId); if (session.isEmpty()) continue; // App might be deleted after call to listApplications() or not activated yet (bootstrap phase) - FileReference appFileReference = session.get().getApplicationPackageReference(); - if (appFileReference != null) { + Optional<FileReference> appFileReference = session.get().getApplicationPackageReference(); + if (appFileReference.isPresent()) { long sessionId = session.get().getSessionId(); attempts++; - if (!fileReferenceExistsOnDisk(downloadDirectory, appFileReference)) { + if (!fileReferenceExistsOnDisk(downloadDirectory, appFileReference.get())) { log.fine(() -> "Downloading application package with file reference " + appFileReference + " for " + applicationId + " (session " + sessionId + ")"); - FileReferenceDownload download = new FileReferenceDownload(appFileReference, + FileReferenceDownload download = new FileReferenceDownload(appFileReference.get(), this.getClass().getSimpleName(), false); if (fileDownloader.getFile(download).isEmpty()) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java index eee7d6ec63d..d26a22284c0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java @@ -518,7 +518,7 @@ public class RpcServer implements Runnable, ConfigActivationListener, TenantList request.parameters().add(new StringValue(fileData.filename())); request.parameters().add(new StringValue(fileData.type().name())); request.parameters().add(new Int64Value(fileData.size())); - // Only add paramter if not gzip, this is default and old clients will not handle the extra parameter + // Only add parameter if not gzip, this is default and old clients will not handle the extra parameter if (fileData.compressionType() != CompressionType.gzip) request.parameters().add(new StringValue(fileData.compressionType().name())); return request; @@ -532,7 +532,7 @@ public class RpcServer implements Runnable, ConfigActivationListener, TenantList request.parameters().add(new DataValue(buf)); invokeRpcIfValidConnection(request); if (request.isError()) { - throw new IllegalArgumentException("Failed delivering reference '" + ref.value() + "' to " + + throw new IllegalArgumentException("Failed delivering part of reference '" + ref.value() + "' to " + target.toString() + " with error: '" + request.errorMessage() + "'."); } else { if (request.returnValues().get(0).asInt32() != 0) { @@ -550,7 +550,8 @@ public class RpcServer implements Runnable, ConfigActivationListener, TenantList request.parameters().add(new StringValue(status.getDescription())); invokeRpcIfValidConnection(request); if (request.isError()) { - throw new IllegalArgumentException("Failed delivering reference '" + fileData.fileReference().value() + "' with file '" + fileData.filename() + "' to " + + throw new IllegalArgumentException("Failed delivering eof for reference '" + fileData.fileReference().value() + + "' with file '" + fileData.filename() + "' to " + target.toString() + " with error: '" + request.errorMessage() + "'."); } else { if (request.returnValues().get(0).asInt32() != 0) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java index b627fe9ba3b..eb359f9ffc6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java @@ -94,14 +94,10 @@ public abstract class Session implements Comparable<Session> { * @return log preamble */ public String logPre() { - Optional<ApplicationId> applicationId; + Optional<ApplicationId> applicationId = getOptionalApplicationId(); + // We might not be able to read application id from zookeeper // e.g. when the app has been deleted. Use tenant name in that case. - try { - applicationId = Optional.of(getApplicationId()); - } catch (Exception e) { - applicationId = Optional.empty(); - } return applicationId .filter(appId -> ! appId.equals(ApplicationId.defaultId())) .map(TenantRepository::logPre) @@ -116,46 +112,6 @@ public abstract class Session implements Comparable<Session> { return sessionZooKeeperClient.readActivatedTime(); } - public void setApplicationId(ApplicationId applicationId) { - sessionZooKeeperClient.writeApplicationId(applicationId); - } - - void setApplicationPackageReference(FileReference applicationPackageReference) { - sessionZooKeeperClient.writeApplicationPackageReference(Optional.ofNullable(applicationPackageReference)); - } - - public void setVespaVersion(Version version) { - sessionZooKeeperClient.writeVespaVersion(version); - } - - public void setDockerImageRepository(Optional<DockerImage> dockerImageRepository) { - sessionZooKeeperClient.writeDockerImageRepository(dockerImageRepository); - } - - public void setAthenzDomain(Optional<AthenzDomain> athenzDomain) { - sessionZooKeeperClient.writeAthenzDomain(athenzDomain); - } - - public void setQuota(Optional<Quota> quota) { - sessionZooKeeperClient.writeQuota(quota); - } - - public void setTenantSecretStores(List<TenantSecretStore> tenantSecretStores) { - sessionZooKeeperClient.writeTenantSecretStores(tenantSecretStores); - } - - public void setOperatorCertificates(List<X509Certificate> operatorCertificates) { - sessionZooKeeperClient.writeOperatorCertificates(operatorCertificates); - } - - public void setCloudAccount(Optional<CloudAccount> cloudAccount) { - sessionZooKeeperClient.writeCloudAccount(cloudAccount); - } - - public void setDataplaneTokens(List<DataplaneToken> dataplaneTokens) { - sessionZooKeeperClient.writeDataplaneTokens(dataplaneTokens); - } - /** Returns application id read from ZooKeeper. Will throw RuntimeException if not found */ public ApplicationId getApplicationId() { return sessionZooKeeperClient.readApplicationId(); } @@ -168,7 +124,7 @@ public abstract class Session implements Comparable<Session> { } } - public FileReference getApplicationPackageReference() {return sessionZooKeeperClient.readApplicationPackageReference(); } + public Optional<FileReference> getApplicationPackageReference() { return sessionZooKeeperClient.readApplicationPackageReference(); } public Optional<DockerImage> getDockerImageRepository() { return sessionZooKeeperClient.readDockerImageRepository(); } @@ -202,6 +158,8 @@ public abstract class Session implements Comparable<Session> { return sessionZooKeeperClient.readDataplaneTokens(); } + public SessionZooKeeperClient getSessionZooKeeperClient() { return sessionZooKeeperClient; } + private Transaction createSetStatusTransaction(Status status) { return sessionZooKeeperClient.createWriteStatusTransaction(status); } @@ -226,7 +184,7 @@ public abstract class Session implements Comparable<Session> { return getApplicationPackage().getFile(relativePath); } - Optional<ApplicationSet> applicationSet() { return Optional.empty(); }; + Optional<ApplicationSet> applicationSet() { return Optional.empty(); } private void markSessionEdited() { setStatus(Session.Status.NEW); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java new file mode 100644 index 00000000000..1fb72e1253e --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java @@ -0,0 +1,87 @@ +package com.yahoo.vespa.config.server.session; + +import com.yahoo.component.Version; +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.Quota; +import com.yahoo.config.model.api.TenantSecretStore; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; +import com.yahoo.config.provision.CloudAccount; +import com.yahoo.config.provision.DataplaneToken; +import com.yahoo.config.provision.DockerImage; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; +import com.yahoo.slime.SlimeUtils; +import com.yahoo.vespa.config.server.tenant.DataplaneTokenSerializer; +import com.yahoo.vespa.config.server.tenant.OperatorCertificateSerializer; +import com.yahoo.vespa.config.server.tenant.TenantSecretStoreSerializer; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Optional; + +/** + * Data class for session information, typically parameters supplied in a deployment request that needs + * to be persisted in ZooKeeper. These will be used when creating a new session based on an existing one. + * + * @author hmusum + */ +public record SessionData(ApplicationId applicationId, + Optional<FileReference> applicationPackageReference, + Version version, + Optional<DockerImage> dockerImageRepository, + Optional<AthenzDomain> athenzDomain, + Optional<Quota> quota, + List<TenantSecretStore> tenantSecretStores, + List<X509Certificate> operatorCertificates, + Optional<CloudAccount> cloudAccount, + List<DataplaneToken> dataplaneTokens) { + + // NOTE: Any state added here MUST also be propagated in com.yahoo.vespa.config.server.deploy.Deployment.prepare() + static final String APPLICATION_ID_PATH = "applicationId"; + static final String APPLICATION_PACKAGE_REFERENCE_PATH = "applicationPackageReference"; + static final String VERSION_PATH = "version"; + static final String CREATE_TIME_PATH = "createTime"; + static final String DOCKER_IMAGE_REPOSITORY_PATH = "dockerImageRepository"; + static final String ATHENZ_DOMAIN = "athenzDomain"; + static final String QUOTA_PATH = "quota"; + static final String TENANT_SECRET_STORES_PATH = "tenantSecretStores"; + static final String OPERATOR_CERTIFICATES_PATH = "operatorCertificates"; + static final String CLOUD_ACCOUNT_PATH = "cloudAccount"; + static final String DATAPLANE_TOKENS_PATH = "dataplaneTokens"; + static final String SESSION_DATA_PATH = "sessionData"; + + public byte[] toJson() { + try { + Slime slime = new Slime(); + toSlime(slime.setObject()); + return SlimeUtils.toJsonBytes(slime); + } + catch (IOException e) { + throw new RuntimeException("Serialization of session data to json failed", e); + } + } + + private void toSlime(Cursor object) { + object.setString(APPLICATION_ID_PATH, applicationId.serializedForm()); + applicationPackageReference.ifPresent(ref -> object.setString(APPLICATION_PACKAGE_REFERENCE_PATH, ref.value())); + object.setString(VERSION_PATH, version.toString()); + object.setLong(CREATE_TIME_PATH, System.currentTimeMillis()); + dockerImageRepository.ifPresent(image -> object.setString(DOCKER_IMAGE_REPOSITORY_PATH, image.asString())); + athenzDomain.ifPresent(domain -> object.setString(ATHENZ_DOMAIN, domain.value())); + quota.ifPresent(q -> q.toSlime(object.setObject(QUOTA_PATH))); + + Cursor tenantSecretStoresArray = object.setArray(TENANT_SECRET_STORES_PATH); + TenantSecretStoreSerializer.toSlime(tenantSecretStores, tenantSecretStoresArray); + + Cursor operatorCertificatesArray = object.setArray(OPERATOR_CERTIFICATES_PATH); + OperatorCertificateSerializer.toSlime(operatorCertificates, operatorCertificatesArray); + + cloudAccount.ifPresent(account -> object.setString(CLOUD_ACCOUNT_PATH, account.value())); + + Cursor dataplaneTokensArray = object.setArray(DATAPLANE_TOKENS_PATH); + DataplaneTokenSerializer.toSlime(dataplaneTokens, dataplaneTokensArray); + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index ae87a0dd182..8d45ac7e8f1 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -36,6 +36,7 @@ import com.yahoo.vespa.config.server.ConfigServerSpec; import com.yahoo.vespa.config.server.TimeoutBudget; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; +import com.yahoo.vespa.config.server.deploy.ZooKeeperClient; import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer; import com.yahoo.vespa.config.server.filedistribution.FileDistributionFactory; import com.yahoo.vespa.config.server.host.HostValidator; @@ -49,7 +50,9 @@ import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore; import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.flags.BooleanFlag; import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.model.application.validation.BundleValidator; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; @@ -71,6 +74,8 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.zip.ZipException; +import static com.yahoo.vespa.config.server.session.SessionZooKeeperClient.getSessionPath; + /** * A SessionPreparer is responsible for preparing a session given an application package. * @@ -90,6 +95,7 @@ public class SessionPreparer { private final SecretStore secretStore; private final FlagSource flagSource; private final ExecutorService executor; + private final BooleanFlag writeSessionData; public SessionPreparer(ModelFactoryRegistry modelFactoryRegistry, FileDistributionFactory fileDistributionFactory, @@ -111,6 +117,7 @@ public class SessionPreparer { this.secretStore = secretStore; this.flagSource = flagSource; this.executor = executor; + this.writeSessionData = Flags.WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB.bindTo(flagSource); } ExecutorService getExecutor() { return executor; } @@ -335,7 +342,7 @@ public class SessionPreparer { writeStateToZooKeeper(sessionZooKeeperClient, preprocessedApplicationPackage, applicationId, - filereference, + Optional.of(filereference), dockerImageRepository, vespaVersion, logger, @@ -377,7 +384,7 @@ public class SessionPreparer { private void writeStateToZooKeeper(SessionZooKeeperClient zooKeeperClient, ApplicationPackage applicationPackage, ApplicationId applicationId, - FileReference fileReference, + Optional<FileReference> fileReference, Optional<DockerImage> dockerImageRepository, Version vespaVersion, DeployLogger deployLogger, @@ -389,20 +396,22 @@ public class SessionPreparer { List<X509Certificate> operatorCertificates, Optional<CloudAccount> cloudAccount, List<DataplaneToken> dataplaneTokens) { - ZooKeeperDeployer zkDeployer = zooKeeperClient.createDeployer(deployLogger); + Path sessionPath = getSessionPath(applicationId.tenant(), zooKeeperClient.sessionId()); + ZooKeeperDeployer zkDeployer = new ZooKeeperDeployer(new ZooKeeperClient(curator, deployLogger, sessionPath)); try { zkDeployer.deploy(applicationPackage, fileRegistryMap, allocatedHosts); - // Note: When changing the below you need to also change similar calls in SessionRepository.createSessionFromExisting() - zooKeeperClient.writeApplicationId(applicationId); - zooKeeperClient.writeApplicationPackageReference(Optional.of(fileReference)); - zooKeeperClient.writeVespaVersion(vespaVersion); - zooKeeperClient.writeDockerImageRepository(dockerImageRepository); - zooKeeperClient.writeAthenzDomain(athenzDomain); - zooKeeperClient.writeQuota(quota); - zooKeeperClient.writeTenantSecretStores(tenantSecretStores); - zooKeeperClient.writeOperatorCertificates(operatorCertificates); - zooKeeperClient.writeCloudAccount(cloudAccount); - zooKeeperClient.writeDataplaneTokens(dataplaneTokens); + new SessionSerializer().write(zooKeeperClient, + applicationId, + fileReference, + dockerImageRepository, + vespaVersion, + athenzDomain, + quota, + tenantSecretStores, + operatorCertificates, + cloudAccount, + dataplaneTokens, + writeSessionData); } catch (RuntimeException | IOException e) { zkDeployer.cleanup(); throw new RuntimeException("Error preparing session", e); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java index f82aa405380..1af728919d9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java @@ -6,7 +6,6 @@ import com.google.common.collect.Multiset; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.concurrent.DaemonThreadFactory; import com.yahoo.concurrent.StripedExecutor; -import com.yahoo.config.FileReference; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.api.ConfigDefinitionRepo; @@ -27,7 +26,6 @@ import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; -import com.yahoo.vespa.config.server.filedistribution.FileDirectory; import com.yahoo.vespa.config.server.filedistribution.FileDistributionFactory; import com.yahoo.vespa.config.server.http.InvalidApplicationException; import com.yahoo.vespa.config.server.http.UnknownVespaVersionException; @@ -41,7 +39,9 @@ import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.zookeeper.SessionCounter; import com.yahoo.vespa.config.server.zookeeper.ZKApplication; import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.flags.BooleanFlag; import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.LongFlag; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.flags.UnboundStringFlag; @@ -127,6 +127,7 @@ public class SessionRepository { private final ConfigDefinitionRepo configDefinitionRepo; private final int maxNodeSize; private final LongFlag expiryTimeFlag; + private final BooleanFlag writeSessionData; public SessionRepository(TenantName tenantName, TenantApplications applicationRepo, @@ -168,7 +169,8 @@ public class SessionRepository { this.modelFactoryRegistry = modelFactoryRegistry; this.configDefinitionRepo = configDefinitionRepo; this.maxNodeSize = maxNodeSize; - expiryTimeFlag = PermanentFlags.CONFIG_SERVER_SESSION_EXPIRY_TIME.bindTo(flagSource); + this.expiryTimeFlag = PermanentFlags.CONFIG_SERVER_SESSION_EXPIRY_TIME.bindTo(flagSource); + this.writeSessionData = Flags.WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB.bindTo(flagSource); loadSessions(); // Needs to be done before creating cache below this.directoryCache = curator.createDirectoryCache(sessionsPath.getAbsolute(), false, false, zkCacheExecutor); @@ -266,24 +268,14 @@ public class SessionRepository { boolean internalRedeploy, TimeoutBudget timeoutBudget, DeployLogger deployLogger) { - ApplicationId existingApplicationId = existingSession.getApplicationId(); + ApplicationId applicationId = existingSession.getApplicationId(); File existingApp = getSessionAppDir(existingSession.getSessionId()); LocalSession session = createSessionFromApplication(existingApp, - existingApplicationId, + applicationId, internalRedeploy, timeoutBudget, deployLogger); - // Note: Setters below need to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper() - session.setApplicationId(existingApplicationId); - session.setApplicationPackageReference(existingSession.getApplicationPackageReference()); - session.setVespaVersion(existingSession.getVespaVersion()); - session.setDockerImageRepository(existingSession.getDockerImageRepository()); - session.setAthenzDomain(existingSession.getAthenzDomain()); - session.setQuota(existingSession.getQuota()); - session.setTenantSecretStores(existingSession.getTenantSecretStores()); - session.setOperatorCertificates(existingSession.getOperatorCertificates()); - session.setCloudAccount(existingSession.getCloudAccount()); - session.setDataplaneTokens(existingSession.getDataplaneTokens()); + write(existingSession, session, applicationId); return session; } @@ -534,7 +526,6 @@ public class SessionRepository { private ApplicationSet loadApplication(Session session, Optional<ApplicationSet> previousApplicationSet) { log.log(Level.FINE, () -> "Loading application for " + session); SessionZooKeeperClient sessionZooKeeperClient = createSessionZooKeeperClient(session.getSessionId()); - ApplicationPackage applicationPackage = sessionZooKeeperClient.loadApplicationPackage(); ActivatedModelsBuilder builder = new ActivatedModelsBuilder(session.getTenantName(), session.getSessionId(), sessionZooKeeperClient, @@ -550,9 +541,9 @@ public class SessionRepository { modelFactoryRegistry, configDefinitionRepo); return ApplicationSet.fromList(builder.buildModels(session.getApplicationId(), - sessionZooKeeperClient.readDockerImageRepository(), - sessionZooKeeperClient.readVespaVersion(), - applicationPackage, + session.getDockerImageRepository(), + session.getVespaVersion(), + sessionZooKeeperClient.loadApplicationPackage(), new AllocatedHostsFromAllModels(), clock.instant())); } @@ -578,6 +569,24 @@ public class SessionRepository { }); } + // ---------------- Serialization ---------------------------------------------------------------- + + private void write(Session existingSession, LocalSession session, ApplicationId applicationId) { + SessionSerializer sessionSerializer = new SessionSerializer(); + sessionSerializer.write(session.getSessionZooKeeperClient(), + applicationId, + existingSession.getApplicationPackageReference(), + existingSession.getDockerImageRepository(), + existingSession.getVespaVersion(), + existingSession.getAthenzDomain(), + existingSession.getQuota(), + existingSession.getTenantSecretStores(), + existingSession.getOperatorCertificates(), + existingSession.getCloudAccount(), + existingSession.getDataplaneTokens(), + writeSessionData); + } + // ---------------- Common stuff ---------------------------------------------------------------- public void deleteExpiredSessions(Map<ApplicationId, Long> activeSessions) { @@ -854,23 +863,18 @@ public class SessionRepository { } SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId); - FileReference fileReference = sessionZKClient.readApplicationPackageReference(); + var fileReference = sessionZKClient.readApplicationPackageReference(); log.log(Level.FINE, () -> "File reference for session id " + sessionId + ": " + fileReference); - if (fileReference == null) return; + if (fileReference.isEmpty()) return; + + Optional<File> sessionDir = fileDistributionFactory.fileDirectory().getFile(fileReference.get()); + // We cannot be guaranteed that the file reference exists (it could be that it has not + // been downloaded yet), and e.g. when bootstrapping we cannot throw an exception in that case + if (sessionDir.isEmpty()) return; - File sessionDir; - FileDirectory fileDirectory = fileDistributionFactory.fileDirectory(); - try { - sessionDir = fileDirectory.getFile(fileReference); - } catch (IllegalArgumentException e) { - // We cannot be guaranteed that the file reference exists (it could be that it has not - // been downloaded yet), and e.g. when bootstrapping we cannot throw an exception in that case - log.log(Level.FINE, () -> "File reference for session id " + sessionId + ": " + fileReference + " not found"); - return; - } ApplicationId applicationId = sessionZKClient.readApplicationId(); log.log(Level.FINE, () -> "Creating local session for tenant '" + tenantName + "' with session id " + sessionId); - createLocalSession(sessionDir, applicationId, sessionId); + createLocalSession(sessionDir.get(), applicationId, sessionId); } private Optional<Long> getActiveSessionId(ApplicationId applicationId) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java new file mode 100644 index 00000000000..1202b2bd08b --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java @@ -0,0 +1,53 @@ +package com.yahoo.vespa.config.server.session; + +import com.yahoo.component.Version; +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.Quota; +import com.yahoo.config.model.api.TenantSecretStore; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; +import com.yahoo.config.provision.CloudAccount; +import com.yahoo.config.provision.DataplaneToken; +import com.yahoo.config.provision.DockerImage; +import com.yahoo.vespa.flags.BooleanFlag; + +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Optional; + +/** + * Serialization and deserialization of session data to/from ZooKeeper. + * @author hmusum + */ +public class SessionSerializer { + + void write(SessionZooKeeperClient zooKeeperClient, ApplicationId applicationId, + Optional<FileReference> fileReference, Optional<DockerImage> dockerImageRepository, + Version vespaVersion, Optional<AthenzDomain> athenzDomain, Optional<Quota> quota, + List<TenantSecretStore> tenantSecretStores, List<X509Certificate> operatorCertificates, + Optional<CloudAccount> cloudAccount, List<DataplaneToken> dataplaneTokens, + BooleanFlag writeSessionData) { + zooKeeperClient.writeApplicationId(applicationId); + zooKeeperClient.writeApplicationPackageReference(fileReference); + zooKeeperClient.writeVespaVersion(vespaVersion); + zooKeeperClient.writeDockerImageRepository(dockerImageRepository); + zooKeeperClient.writeAthenzDomain(athenzDomain); + zooKeeperClient.writeQuota(quota); + zooKeeperClient.writeTenantSecretStores(tenantSecretStores); + zooKeeperClient.writeOperatorCertificates(operatorCertificates); + zooKeeperClient.writeCloudAccount(cloudAccount); + zooKeeperClient.writeDataplaneTokens(dataplaneTokens); + if (writeSessionData.value()) + zooKeeperClient.writeSessionData(new SessionData(applicationId, + fileReference, + vespaVersion, + dockerImageRepository, + athenzDomain, + quota, + tenantSecretStores, + operatorCertificates, + cloudAccount, + dataplaneTokens)); + } + +}
\ No newline at end of file diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java index 23b6fe075fa..7d1a7ceae4e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java @@ -6,7 +6,6 @@ import com.yahoo.component.Version; import com.yahoo.component.Vtag; import com.yahoo.config.FileReference; import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.Quota; import com.yahoo.config.model.api.TenantSecretStore; @@ -23,8 +22,6 @@ import com.yahoo.text.Utf8; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.config.server.NotFoundException; import com.yahoo.vespa.config.server.UserConfigDefinitionRepo; -import com.yahoo.vespa.config.server.deploy.ZooKeeperClient; -import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer; import com.yahoo.vespa.config.server.filedistribution.AddFileInterface; import com.yahoo.vespa.config.server.filedistribution.MockFileManager; import com.yahoo.vespa.config.server.tenant.CloudAccountSerializer; @@ -45,6 +42,18 @@ import java.util.List; import java.util.Optional; import java.util.logging.Level; +import static com.yahoo.vespa.config.server.session.SessionData.APPLICATION_ID_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.APPLICATION_PACKAGE_REFERENCE_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.ATHENZ_DOMAIN; +import static com.yahoo.vespa.config.server.session.SessionData.CLOUD_ACCOUNT_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.CREATE_TIME_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.DATAPLANE_TOKENS_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.DOCKER_IMAGE_REPOSITORY_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.OPERATOR_CERTIFICATES_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.QUOTA_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.SESSION_DATA_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.TENANT_SECRET_STORES_PATH; +import static com.yahoo.vespa.config.server.session.SessionData.VERSION_PATH; import static com.yahoo.vespa.config.server.zookeeper.ZKApplication.USER_DEFCONFIGS_ZK_SUBPATH; import static com.yahoo.vespa.curator.Curator.CompletionWaiter; import static com.yahoo.yolean.Exceptions.uncheck; @@ -61,18 +70,6 @@ public class SessionZooKeeperClient { // NOTE: Any state added here MUST also be propagated in com.yahoo.vespa.config.server.deploy.Deployment.prepare() - static final String APPLICATION_ID_PATH = "applicationId"; - static final String APPLICATION_PACKAGE_REFERENCE_PATH = "applicationPackageReference"; - private static final String VERSION_PATH = "version"; - private static final String CREATE_TIME_PATH = "createTime"; - private static final String DOCKER_IMAGE_REPOSITORY_PATH = "dockerImageRepository"; - private static final String ATHENZ_DOMAIN = "athenzDomain"; - private static final String QUOTA_PATH = "quota"; - private static final String TENANT_SECRET_STORES_PATH = "tenantSecretStores"; - private static final String OPERATOR_CERTIFICATES_PATH = "operatorCertificates"; - private static final String CLOUD_ACCOUNT_PATH = "cloudAccount"; - private static final String DATAPLANE_TOKENS_PATH = "dataplaneTokens"; - private final Curator curator; private final TenantName tenantName; private final long sessionId; @@ -180,11 +177,8 @@ public class SessionZooKeeperClient { reference -> curator.set(applicationPackageReferencePath(), Utf8.toBytes(reference.value()))); } - FileReference readApplicationPackageReference() { - Optional<byte[]> data = curator.getData(applicationPackageReferencePath()); - if (data.isEmpty()) return null; // This should not happen. - - return new FileReference(Utf8.toString(data.get())); + Optional<FileReference> readApplicationPackageReference() { + return curator.getData(applicationPackageReferencePath()).map(d -> new FileReference(Utf8.toString(d))); } private Path applicationPackageReferencePath() { @@ -227,6 +221,10 @@ public class SessionZooKeeperClient { curator.set(versionPath(), Utf8.toBytes(version.toString())); } + public void writeSessionData(SessionData sessionData) { + curator.set(sessionPath.append(SESSION_DATA_PATH), sessionData.toJson()); + } + public Version readVespaVersion() { Optional<byte[]> data = curator.getData(versionPath()); // TODO: Empty version should not be possible any more - verify and remove @@ -261,11 +259,6 @@ public class SessionZooKeeperClient { .orElseThrow(() -> new IllegalStateException("Allocated hosts does not exists")); } - public ZooKeeperDeployer createDeployer(DeployLogger logger) { - ZooKeeperClient zkClient = new ZooKeeperClient(curator, logger, sessionPath); - return new ZooKeeperDeployer(zkClient); - } - public Transaction createWriteStatusTransaction(Session.Status status) { CuratorTransaction transaction = new CuratorTransaction(curator); if (curator.exists(sessionStatusPath)) { @@ -368,7 +361,7 @@ public class SessionZooKeeperClient { transaction.commit(); } - private static Path getSessionPath(TenantName tenantName, long sessionId) { + static Path getSessionPath(TenantName tenantName, long sessionId) { return TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId)); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DataplaneTokenSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DataplaneTokenSerializer.java index ef41512f979..3b819da6237 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DataplaneTokenSerializer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/DataplaneTokenSerializer.java @@ -54,6 +54,11 @@ public class DataplaneTokenSerializer { public static Slime toSlime(List<DataplaneToken> dataplaneTokens) { Slime slime = new Slime(); Cursor root = slime.setArray(); + toSlime(dataplaneTokens, root); + return slime; + } + + public static void toSlime(List<DataplaneToken> dataplaneTokens, Cursor root) { for (DataplaneToken token : dataplaneTokens) { Cursor cursor = root.addObject(); cursor.setString(ID_FIELD, token.tokenId()); @@ -65,6 +70,6 @@ public class DataplaneTokenSerializer { val.setString(EXPIRATION_FIELD, v.expiration().map(Instant::toString).orElse("<none>")); }); } - return slime; } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/OperatorCertificateSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/OperatorCertificateSerializer.java index 232dd2e5fe7..e5a969bb948 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/OperatorCertificateSerializer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/OperatorCertificateSerializer.java @@ -1,8 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - package com.yahoo.vespa.config.server.tenant; -import com.yahoo.config.model.api.ApplicationRoles; import com.yahoo.security.X509CertificateUtils; import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; @@ -11,21 +9,28 @@ import com.yahoo.slime.SlimeUtils; import java.security.cert.X509Certificate; import java.util.List; -import java.util.stream.Collectors; +/** + * Serializer for operator certificates. + * The certificates are serialized as a list of PEM strings. + * @author tokle + */ public class OperatorCertificateSerializer { private final static String certificateField = "certificates"; - public static Slime toSlime(List<X509Certificate> certificateList) { Slime slime = new Slime(); var root = slime.setObject(); Cursor array = root.setArray(certificateField); + toSlime(certificateList, array); + return slime; + } + + public static void toSlime(List<X509Certificate> certificateList, Cursor array) { certificateList.stream() .map(X509CertificateUtils::toPem) .forEach(array::addString); - return slime; } public static List<X509Certificate> fromSlime(Inspector object) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java index 262192ad6c4..b8df5073a3e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantSecretStoreSerializer.java @@ -30,10 +30,14 @@ public class TenantSecretStoreSerializer { public static Slime toSlime(List<TenantSecretStore> tenantSecretStores) { Slime slime = new Slime(); Cursor cursor = slime.setArray(); - tenantSecretStores.forEach(tenantSecretStore -> toSlime(tenantSecretStore, cursor.addObject())); + toSlime(tenantSecretStores, cursor); return slime; } + public static void toSlime(List<TenantSecretStore> tenantSecretStores, Cursor cursor) { + tenantSecretStores.forEach(tenantSecretStore -> toSlime(tenantSecretStore, cursor.addObject())); + } + public static void toSlime(TenantSecretStore tenantSecretStore, Cursor object) { object.setString(awsIdField, tenantSecretStore.getAwsId()); object.setString(nameField, tenantSecretStore.getName()); |