diff options
Diffstat (limited to 'configserver')
58 files changed, 1004 insertions, 356 deletions
diff --git a/configserver/pom.xml b/configserver/pom.xml index 8776fbd5ad1..30d92dc7650 100644 --- a/configserver/pom.xml +++ b/configserver/pom.xml @@ -126,6 +126,11 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> + <artifactId>filedistribution</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> <artifactId>filedistributionmanager</artifactId> <version>${project.version}</version> </dependency> @@ -172,6 +177,11 @@ <artifactId>jersey-proxy-client</artifactId> </dependency> <dependency> + <groupId>net.jpountz.lz4</groupId> + <artifactId>lz4</artifactId> + <scope>compile</scope> + </dependency> + <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/PathProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/PathProvider.java deleted file mode 100644 index 5910c69048a..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/PathProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server; - -import com.google.inject.Inject; -import com.yahoo.path.Path; - -/** - * Temporary provider of root path for components that will soon get them injected from a parent class. - * - * @author lulf - * * @since 5.1.24 - */ -public class PathProvider { - - private final Path root; - // Path for Vespa-related data stored in Zookeeper (subpaths are relative to this path) - // NOTE: This should not be exposed, as this path can be different in testing, depending on how we configure it. - private static final String APPS_ZK_NODE = "sessions"; - private static final String VESPA_ZK_PATH = "/vespa/config"; - - @Inject - public PathProvider() { - root = Path.fromString(VESPA_ZK_PATH); - } - - public PathProvider(Path root) { - this.root = root; - } - - public Path getRoot() { - return root; - } - - public Path getSessionDirs() { - return root.append(APPS_ZK_NODE); - } - - public Path getSessionDir(long sessionId) { - return getSessionDirs().append(String.valueOf(sessionId)); - } - -} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java index 648e6bb7180..c3d13b86591 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ZKTenantApplications.java @@ -18,11 +18,15 @@ import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** @@ -36,10 +40,13 @@ import java.util.logging.Logger; public class ZKTenantApplications implements TenantApplications, PathChildrenCacheListener { private static final Logger log = Logger.getLogger(ZKTenantApplications.class.getName()); + private static final Duration checkForRemovedApplicationsInterval = Duration.ofMinutes(1); + private final Curator curator; private final Path applicationsPath; private final ExecutorService pathChildrenExecutor = Executors.newFixedThreadPool(1, ThreadFactoryFactory.getThreadFactory(ZKTenantApplications.class.getName())); + private final ScheduledExecutorService checkForRemovedApplicationsService = new ScheduledThreadPoolExecutor(1); private final Curator.DirectoryCache directoryCache; private final ReloadHandler reloadHandler; private final TenantName tenant; @@ -47,16 +54,21 @@ public class ZKTenantApplications implements TenantApplications, PathChildrenCac private ZKTenantApplications(Curator curator, Path applicationsPath, ReloadHandler reloadHandler, TenantName tenant) { this.curator = curator; this.applicationsPath = applicationsPath; + curator.create(applicationsPath); this.reloadHandler = reloadHandler; this.tenant = tenant; this.directoryCache = curator.createDirectoryCache(applicationsPath.getAbsolute(), false, false, pathChildrenExecutor); this.directoryCache.start(); this.directoryCache.addListener(this); + checkForRemovedApplicationsService.scheduleWithFixedDelay(this::removeApplications, + checkForRemovedApplicationsInterval.getSeconds(), + checkForRemovedApplicationsInterval.getSeconds(), + TimeUnit.SECONDS); } - public static TenantApplications create(Curator curator, Path applicationsPath, ReloadHandler reloadHandler, TenantName tenant) { + public static TenantApplications create(Curator curator, ReloadHandler reloadHandler, TenantName tenant) { try { - return new ZKTenantApplications(curator, applicationsPath, reloadHandler, tenant); + return new ZKTenantApplications(curator, Tenants.getApplicationsPath(tenant), reloadHandler, tenant); } catch (Exception e) { throw new RuntimeException(Tenants.logPre(tenant) + "Error creating application repo", e); } @@ -118,6 +130,7 @@ public class ZKTenantApplications implements TenantApplications, PathChildrenCac public void close() { directoryCache.close(); pathChildrenExecutor.shutdown(); + checkForRemovedApplicationsService.shutdown(); } @Override @@ -138,22 +151,22 @@ public class ZKTenantApplications implements TenantApplications, PathChildrenCac } // We might have lost events and might need to remove applications (new applications are // not added by listening for events here, they are added when session is added, see RemoteSessionRepo) - removeApplications(event.getType()); + removeApplications(); } private void applicationRemoved(ApplicationId applicationId) { reloadHandler.removeApplication(applicationId); - log.log(LogLevel.DEBUG, Tenants.logPre(applicationId) + "Application removed: " + applicationId); + log.log(LogLevel.INFO, Tenants.logPre(applicationId) + "Application removed: " + applicationId); } private void applicationAdded(ApplicationId applicationId) { log.log(LogLevel.DEBUG, Tenants.logPre(applicationId) + "Application added: " + applicationId); } - private void removeApplications(PathChildrenCacheEvent.Type eventType) { + private void removeApplications() { ImmutableSet<ApplicationId> activeApplications = ImmutableSet.copyOf(listApplications()); - log.log(LogLevel.DEBUG, "Got " + eventType + " event for tenant '" + tenant + - "', removing applications except these active applications: " + activeApplications); + log.log(LogLevel.DEBUG, "Removing stale applications for tenant '" + tenant + + "', not removing these active applications: " + activeApplications); reloadHandler.removeApplicationsExcept(activeApplications); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 70b677b4057..c0c9c309576 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -2,11 +2,16 @@ package com.yahoo.vespa.config.server.deploy; import com.yahoo.component.Version; -import com.yahoo.config.model.api.*; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.application.api.FileRegistry; +import com.yahoo.config.model.api.ConfigDefinitionRepo; +import com.yahoo.config.model.api.ConfigServerSpec; +import com.yahoo.config.model.api.HostProvisioner; +import com.yahoo.config.model.api.Model; +import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Zone; @@ -132,6 +137,7 @@ public class ModelContextImpl implements ModelContext { private final ApplicationId applicationId; private final boolean multitenant; private final List<ConfigServerSpec> configServerSpecs; + private final HostName loadBalancerName; private final boolean hostedVespa; private final Zone zone; private final Set<Rotation> rotations; @@ -139,12 +145,14 @@ public class ModelContextImpl implements ModelContext { public Properties(ApplicationId applicationId, boolean multitenant, List<ConfigServerSpec> configServerSpecs, + HostName loadBalancerName, boolean hostedVespa, Zone zone, Set<Rotation> rotations) { this.applicationId = applicationId; this.multitenant = multitenant; this.configServerSpecs = configServerSpecs; + this.loadBalancerName = loadBalancerName; this.hostedVespa = hostedVespa; this.zone = zone; this.rotations = rotations; @@ -166,6 +174,11 @@ public class ModelContextImpl implements ModelContext { } @Override + public HostName loadBalancerName() { + return loadBalancerName; + } + + @Override public boolean hostedVespa() { return hostedVespa; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/AddFileInterface.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/AddFileInterface.java new file mode 100644 index 00000000000..61c376a7256 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/AddFileInterface.java @@ -0,0 +1,9 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.config.FileReference; + +public interface AddFileInterface { + FileReference addFile(String relativePath); + FileReference addFile(String relativePath, FileReference reference); +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/ApplicationFileManager.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/ApplicationFileManager.java new file mode 100644 index 00000000000..0d1aae97690 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/ApplicationFileManager.java @@ -0,0 +1,25 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.config.FileReference; +import java.io.File; + +public class ApplicationFileManager implements AddFileInterface { + private final File applicationDir; + private final FileDirectory master; + + ApplicationFileManager(File applicationDir, FileDirectory master) { + this.applicationDir = applicationDir; + this.master = master; + } + + @Override + public FileReference addFile(String relativePath, FileReference reference) { + return master.addFile(new File(applicationDir, relativePath), reference); + } + + @Override + public FileReference addFile(String relativePath) { + return master.addFile(new File(applicationDir, relativePath)); + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java new file mode 100644 index 00000000000..588f2d1d63f --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyDistribution.java @@ -0,0 +1,30 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.FileDistribution; + +import java.util.Collection; +import java.util.Set; + +public class CombinedLegacyDistribution implements FileDistribution { + private final FileDistribution legacy; + + CombinedLegacyDistribution(FileDBHandler legacy) { + this.legacy = legacy; + } + @Override + public void sendDeployedFiles(String hostName, Set<FileReference> fileReferences) { + legacy.sendDeployedFiles(hostName, fileReferences); + } + + @Override + public void reloadDeployFileDistributor() { + legacy.reloadDeployFileDistributor(); + } + + @Override + public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) { + legacy.removeDeploymentsThatHaveDifferentApplicationId(targetHostnames); + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyRegistry.java new file mode 100644 index 00000000000..8f2cb194bbd --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/CombinedLegacyRegistry.java @@ -0,0 +1,32 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.config.FileReference; +import com.yahoo.config.application.api.FileRegistry; + +import java.util.List; + +public class CombinedLegacyRegistry implements FileRegistry { + private final FileDBRegistry legacy; + private final FileDBRegistry future; + + CombinedLegacyRegistry(FileDBRegistry legacy, FileDBRegistry future) { + this.legacy = legacy; + this.future = future; + } + @Override + public FileReference addFile(String relativePath) { + FileReference reference = legacy.addFile(relativePath); + return future.addFile(relativePath, reference); + } + + @Override + public String fileSourceHost() { + return future.fileSourceHost(); + } + + @Override + public List<Entry> export() { + return future.export(); + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java index d7dce9d3f3d..f0ce6104496 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBHandler.java @@ -31,11 +31,6 @@ public class FileDBHandler implements FileDistribution { } @Override - public void limitSendingOfDeployedFilesTo(Collection<String> hostNames) { - manager.limitSendingOfDeployedFilesTo(hostNames); - } - - @Override public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) { manager.removeDeploymentsThatHaveDifferentApplicationId(targetHostnames); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBRegistry.java index d921d8d4f8d..1a76454fbed 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBRegistry.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDBRegistry.java @@ -4,29 +4,41 @@ package com.yahoo.vespa.config.server.filedistribution; import com.yahoo.config.FileReference; import com.yahoo.config.application.api.FileRegistry; import com.yahoo.net.HostName; -import com.yahoo.vespa.filedistribution.FileDistributionManager; -import com.yahoo.config.model.application.provider.FileReferenceCreator; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; /** * @author tonytv */ public class FileDBRegistry implements FileRegistry { - private final FileDistributionManager manager; + private final AddFileInterface manager; private List<Entry> entries = new ArrayList<>(); private final Map<String, FileReference> fileReferenceCache = new HashMap<>(); - public FileDBRegistry(FileDistributionManager manager) { + public FileDBRegistry(AddFileInterface manager) { this.manager = manager; } + public synchronized FileReference addFile(String relativePath, FileReference reference) { + Optional<FileReference> cachedReference = Optional.ofNullable(fileReferenceCache.get(relativePath)); + return cachedReference.orElseGet(() -> { + FileReference newRef = manager.addFile(relativePath, reference); + entries.add(new Entry(relativePath, newRef)); + fileReferenceCache.put(relativePath, newRef); + return newRef; + }); + } + @Override public synchronized FileReference addFile(String relativePath) { Optional<FileReference> cachedReference = Optional.ofNullable(fileReferenceCache.get(relativePath)); return cachedReference.orElseGet(() -> { - FileReference newRef = FileReferenceCreator.create(manager.addFile(relativePath)); + FileReference newRef = manager.addFile(relativePath); entries.add(new Entry(relativePath, newRef)); fileReferenceCache.put(relativePath, newRef); return newRef; 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 new file mode 100644 index 00000000000..7d0ba6cd9bd --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java @@ -0,0 +1,120 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.FileDistribution; +import com.yahoo.io.IOUtils; +import com.yahoo.text.Utf8; +import net.jpountz.xxhash.XXHash64; +import net.jpountz.xxhash.XXHashFactory; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Logger; + +public class FileDirectory { + private static final Logger log = Logger.getLogger(FileDirectory.class.getName()); + private final File root; + + public FileDirectory() { + this(FileDistribution.getDefaultFileDBPath()); + } + + public FileDirectory(File rootDir) { + root = rootDir; + try { + ensureRootExist(); + } catch (IllegalArgumentException e) { + log.warning("Failed creating directory in constructor, will retry on demand : " + e.toString()); + } + } + + private void ensureRootExist() { + if (! root.exists()) { + if ( ! root.mkdir()) { + throw new IllegalArgumentException("Failed creating root dir '" + root.getAbsolutePath() + "'."); + } + } else if (!root.isDirectory()) { + throw new IllegalArgumentException("'" + root.getAbsolutePath() + "' is not a directory"); + } + } + + static private class Filter implements FilenameFilter { + @Override + public boolean accept(File dir, String name) { + return !".".equals(name) && !"..".equals(name) ; + } + } + + String getPath(FileReference ref) { + return root.getAbsolutePath() + "/" + ref.value(); + } + + File getFile(FileReference reference) { + ensureRootExist(); + File dir = new File(getPath(reference)); + if (!dir.exists()) { + throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + "' does not exist."); + } + if (!dir.isDirectory()) { + throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + "' is not a directory."); + } + File [] files = dir.listFiles(new Filter()); + if (files.length != 1) { + StringBuilder msg = new StringBuilder(); + for (File f: files) { + msg.append(f.getName()).append("\n"); + } + throw new IllegalArgumentException("File reference '" + reference.toString() + "' with absolute path '" + dir.getAbsolutePath() + " does not contain exactly one file, but [" + msg.toString() + "]"); + } + return files[0]; + } + + private Long computeReference(File file) throws IOException { + byte [] wholeFile = IOUtils.readFileBytes(file); + XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); + return hasher.hash(ByteBuffer.wrap(wholeFile), hasher.hash(ByteBuffer.wrap(Utf8.toBytes(file.getName())), 0)); + } + + public FileReference addFile(File source) { + try { + Long hash = computeReference(source); + FileReference reference = new FileReference(Long.toHexString(hash)); + return addFile(source, reference); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public FileReference addFile(File source, FileReference reference) { + ensureRootExist(); + try { + File destinationDir = new File(root, reference.value()); + if (!destinationDir.exists()) { + destinationDir.mkdir(); + Path tempDestinationDir = Files.createTempDirectory(root.toPath(), "writing"); + File destination = new File(tempDestinationDir.toFile(), source.getName()); + if (source.isDirectory()) + IOUtils.copyDirectory(source, destination); + else + IOUtils.copy(source, destination); + if (!destinationDir.exists()) { + if ( ! tempDestinationDir.toFile().renameTo(destinationDir)) { + log.warning("Failed moving '" + tempDestinationDir.toFile().getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'."); + } + } else { + IOUtils.copyDirectory(tempDestinationDir.toFile(), destinationDir, 1); + } + IOUtils.recursiveDeleteDir(tempDestinationDir.toFile()); + } + return reference; + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java index 3693bfb361c..59c3a54897d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionProvider.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.filedistribution; +import com.yahoo.config.FileReference; import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.application.api.FileRegistry; import com.yahoo.vespa.filedistribution.FileDistributionManager; @@ -19,16 +20,32 @@ public class FileDistributionProvider { private final FileRegistry fileRegistry; private final FileDistribution fileDistribution; - public FileDistributionProvider(File applicationDir, String zooKeepersSpec, String applicationId, Lock fileDistributionLock) { + static private class ManagerWrapper implements AddFileInterface { + private final FileDistributionManager manager; + ManagerWrapper(FileDistributionManager manager) { + this.manager = manager; + } + @Override + public FileReference addFile(String relativePath) { + return new FileReference(manager.addFile(relativePath)); + } + + @Override + public FileReference addFile(String relativePath, FileReference reference) { + throw new IllegalStateException("addFile with external reference is not possible with legacy filedistribution."); + } + } + + public FileDistributionProvider(File applicationDir, String zooKeepersSpec, + String applicationId, Lock fileDistributionLock) + { ensureDirExists(FileDistribution.getDefaultFileDBPath()); final FileDistributionManager manager = new FileDistributionManager( - FileDistribution.getDefaultFileDBPath(), - applicationDir, - zooKeepersSpec, - applicationId, - fileDistributionLock); - this.fileDistribution = new FileDBHandler(manager); - this.fileRegistry = new FileDBRegistry(manager); + FileDistribution.getDefaultFileDBPath(), applicationDir, + zooKeepersSpec, applicationId, fileDistributionLock); + this.fileDistribution = new CombinedLegacyDistribution(new FileDBHandler(manager)); + this.fileRegistry = new CombinedLegacyRegistry(new FileDBRegistry(new ManagerWrapper(manager)), + new FileDBRegistry(new ApplicationFileManager(applicationDir, new FileDirectory()))); } public FileDistributionProvider(FileRegistry fileRegistry, FileDistribution fileDistribution) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java new file mode 100644 index 00000000000..9316a9a5c8e --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileServer.java @@ -0,0 +1,145 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.filedistribution; + +import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.FileReference; +import com.yahoo.config.model.api.FileDistribution; +import com.yahoo.config.subscription.ConfigSourceSet; +import com.yahoo.io.IOUtils; +import com.yahoo.jrt.Supervisor; +import com.yahoo.jrt.Transport; +import com.yahoo.net.HostName; +import com.yahoo.vespa.config.Connection; +import com.yahoo.vespa.config.ConnectionPool; +import com.yahoo.vespa.config.JRTConnectionPool; +import com.yahoo.vespa.config.server.ConfigServerSpec; +import com.yahoo.vespa.filedistribution.FileDownloader; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class FileServer { + private static final Logger log = Logger.getLogger(FileServer.class.getName()); + private final FileDirectory root; + private final ExecutorService executor; + private final FileDownloader downloader; + + public static class ReplayStatus { + private final int code; + private final String description; + public ReplayStatus(int code, String description) { + this.code = code; + this.description = description; + } + public boolean ok() { return code == 0; } + public int getCode() { return code; } + public String getDescription() { return description; } + } + + public interface Receiver { + void receive(FileReference reference, String filename, byte [] content, ReplayStatus status); + } + + @Inject + public FileServer(ConfigserverConfig configserverConfig) { + this(createConnectionPool(configserverConfig), FileDistribution.getDefaultFileDBPath()); + } + + // For testing only + public FileServer(File rootDir) { + this(new EmptyConnectionPool(), rootDir); + } + + private FileServer(ConnectionPool connectionPool, File rootDir) { + this.downloader = new FileDownloader(connectionPool); + this.root = new FileDirectory(rootDir); + this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + } + + public boolean hasFile(String fileName) { + return hasFile(new FileReference(fileName)); + } + public boolean hasFile(FileReference reference) { + try { + return root.getFile(reference).exists(); + } catch (IllegalArgumentException e) { + log.warning("Failed locating file reference '" + reference + "' with error " + e.toString()); + } + return false; + } + public boolean startFileServing(String fileName, Receiver target) { + FileReference reference = new FileReference(fileName); + File file = root.getFile(reference); + + if (file.exists()) { + executor.execute(() -> serveFile(reference, target)); + } + return false; + } + + private void serveFile(FileReference reference, Receiver target) { + File file = root.getFile(reference); + // TODO remove once verified in system tests. + log.info("Start serving reference '" + reference.value() + "' with file '" + file.getAbsolutePath() + "'"); + byte [] blob = new byte [0]; + boolean success = false; + String errorDescription = "OK"; + try { + blob = IOUtils.readFileBytes(file); + success = true; + } catch (IOException e) { + errorDescription = "For file reference '" + reference.value() + "' I failed reading file '" + file.getAbsolutePath() + "'"; + log.warning(errorDescription + "for sending to '" + target.toString() + "'. " + e.toString()); + } + target.receive(reference, file.getName(), blob, + new ReplayStatus(success ? 0 : 1, success ? "OK" : errorDescription)); + // TODO remove once verified in system tests. + log.info("Done serving reference '" + reference.toString() + "' with file '" + file.getAbsolutePath() + "'"); + } + + public void download(FileReference fileReference) { + downloader.getFile(fileReference); + } + + public FileDownloader downloader() { + return downloader; + } + + // Connection pool with all config servers except this one (might be an empty pool if there is only one config server) + private static ConnectionPool createConnectionPool(ConfigserverConfig configserverConfig) { + List<String> configServers = ConfigServerSpec.fromConfig(configserverConfig) + .stream() + .filter(spec -> !spec.getHostName().equals(HostName.getLocalhost())) + .map(spec -> "tcp/" + spec.getHostName() + ":" + spec.getConfigServerPort()) + .collect(Collectors.toList()); + + return configServers.size() > 0 ? new JRTConnectionPool(new ConfigSourceSet(configServers)) : new EmptyConnectionPool(); + } + + private static class EmptyConnectionPool implements ConnectionPool { + + @Override + public void close() {} + + @Override + public void setError(Connection connection, int i) {} + + @Override + public Connection getCurrent() { return null; } + + @Override + public Connection setNewCurrentConnection() { return null; } + + @Override + public int getSize() { return 0; } + + @Override + public Supervisor getSupervisor() { return new Supervisor(new Transport()); } + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java index 81c777df393..eb23d38e23e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/MockFileDBHandler.java @@ -14,7 +14,6 @@ import java.util.Set; public class MockFileDBHandler implements FileDistribution { public int sendDeployedFilesCalled = 0; public int reloadDeployFileDistributorCalled = 0; - public int limitSendingOfDeployedFilesToCalled = 0; public int removeDeploymentsThatHaveDifferentApplicationIdCalled = 0; @Override @@ -28,11 +27,6 @@ public class MockFileDBHandler implements FileDistribution { } @Override - public void limitSendingOfDeployedFilesTo(Collection<String> hostNames) { - limitSendingOfDeployedFilesToCalled++; - } - - @Override public void removeDeploymentsThatHaveDifferentApplicationId(Collection<String> targetHostnames) { removeDeploymentsThatHaveDifferentApplicationIdCalled++; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/Utils.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/Utils.java index 873a24b5f05..e5bf8e22020 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/Utils.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/Utils.java @@ -31,9 +31,10 @@ public class Utils { com.yahoo.jdisc.http.HttpRequest jDiscRequest = req.getJDiscRequest(); BindingMatch<?> bm = jDiscRequest.getBindingMatch(); if (bm == null) { + UriPattern pattern = new UriPattern(uriPattern); bm = new BindingMatch<>( - new UriPattern(uriPattern).match(URI.create(jDiscRequest.getUri().toString())), - new Object()); + pattern.match(URI.create(jDiscRequest.getUri().toString())), + new Object(), pattern); } return bm; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpConfigRequests.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpConfigRequests.java index db92f53aacd..59270afd397 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpConfigRequests.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/HttpConfigRequests.java @@ -39,7 +39,7 @@ public class HttpConfigRequests { UriPattern fullAppIdPattern = new UriPattern(pattern); URI uri = req.getUri(); Match match = fullAppIdPattern.match(uri); - if (match!=null) return new BindingMatch<>(match, new Object()); + if (match!=null) return new BindingMatch<>(match, new Object(), fullAppIdPattern); } throw new IllegalArgumentException("Illegal url for config request: " + req.getUri()); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java index 1806414f510..85249d4e87d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java @@ -75,7 +75,7 @@ public class LbServicesProducer implements LbServicesConfig.Producer { serviceInfo.getServiceType().equals("qrserver")). findAny(); if (container.isPresent()) { - activeRotation |= Boolean.valueOf(container.get().getProperty("activeRotation").get()); + activeRotation |= Boolean.valueOf(container.get().getProperty("activeRotation").orElse("false")); } } return activeRotation; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java index af4d998c347..48732814919 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java @@ -6,16 +6,19 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelFactory; +import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationLockException; +import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.OutOfCapacityException; -import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Version; import com.yahoo.config.provision.Zone; import com.yahoo.lang.SettableOptional; +import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.server.ConfigServerSpec; import com.yahoo.vespa.config.server.deploy.ModelContextImpl; +import com.yahoo.vespa.config.server.http.InternalServerException; import com.yahoo.vespa.config.server.http.UnknownVespaVersionException; import com.yahoo.vespa.config.server.provision.StaticProvisioner; @@ -24,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -96,7 +100,12 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { catch (RuntimeException e) { boolean isOldestMajor = i == majorVersions.size() - 1; if (isOldestMajor) { - throw new IllegalArgumentException(applicationId + ": Error loading model", e); + if (e instanceof NullPointerException || e instanceof NoSuchElementException) { + log.log(LogLevel.WARNING, "Unexpected error when building model ", e); + throw new InternalServerException(applicationId + ": Error loading model", e); + } else { + throw new IllegalArgumentException(applicationId + ": Error loading model", e); + } } else { log.log(Level.INFO, applicationId + ": Skipping major version " + majorVersions.get(i), e); } @@ -170,9 +179,10 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { ConfigserverConfig configserverConfig, Zone zone, Set<Rotation> rotations) { - return new ModelContextImpl.Properties(applicationId, + return new ModelContextImpl.Properties(applicationId, configserverConfig.multitenant(), ConfigServerSpec.fromConfig(configserverConfig), + HostName.from(configserverConfig.loadBalancerAddress()), configserverConfig.hostedVespa(), zone, rotations); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java index ffd86ac121e..8aaf053247e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponses.java @@ -76,7 +76,8 @@ public class DelayedConfigResponses { } public synchronized void run() { - remove(); + removeFromQueue(); + removeWatcher(); rpcServer.addToRequestQueue(request, true, null); if (log.isLoggable(LogLevel.DEBUG)) { log.log(LogLevel.DEBUG, logPre()+"DelayedConfigResponse. putting on queue: " + request.getShortDescription()); @@ -86,9 +87,8 @@ public class DelayedConfigResponses { /** * Remove delayed response from its queue */ - private void remove() { + private void removeFromQueue() { delayedResponsesQueue.remove(this); - removeWatcher(); } public JRTServerConfigRequest getRequest() { @@ -107,8 +107,13 @@ public class DelayedConfigResponses { return Tenants.logPre(app); } - public synchronized boolean cancel() { - remove(); + synchronized void cancelAndRemove() { + removeFromQueue(); + cancel(); + } + + synchronized boolean cancel() { + removeWatcher(); if (future == null) { throw new IllegalStateException("Cannot cancel a task that has not been scheduled"); } @@ -129,7 +134,7 @@ public class DelayedConfigResponses { */ @Override public void notifyTargetInvalid(Target target) { - cancel(); + cancelAndRemove(); } private void addWatcher() { 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 3c9917bf17e..d17cdf722ea 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 @@ -4,18 +4,22 @@ package com.yahoo.vespa.config.server.rpc; import com.google.inject.Inject; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.concurrent.ThreadFactoryFactory; +import com.yahoo.config.FileReference; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostLivenessTracker; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.jrt.Acceptor; +import com.yahoo.jrt.DataValue; import com.yahoo.jrt.Int32Value; +import com.yahoo.jrt.Int64Value; import com.yahoo.jrt.ListenFailedException; import com.yahoo.jrt.Method; import com.yahoo.jrt.Request; import com.yahoo.jrt.Spec; import com.yahoo.jrt.StringValue; import com.yahoo.jrt.Supervisor; +import com.yahoo.jrt.Target; import com.yahoo.jrt.Transport; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.ErrorCode; @@ -27,6 +31,7 @@ import com.yahoo.vespa.config.protocol.Trace; import com.yahoo.vespa.config.server.SuperModelRequestHandler; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.GetConfigContext; +import com.yahoo.vespa.config.server.filedistribution.FileServer; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.host.HostRegistry; import com.yahoo.vespa.config.server.ReloadListener; @@ -36,7 +41,10 @@ import com.yahoo.vespa.config.server.monitoring.MetricUpdaterFactory; import com.yahoo.vespa.config.server.tenant.TenantHandlerProvider; import com.yahoo.vespa.config.server.tenant.TenantListener; import com.yahoo.vespa.config.server.tenant.Tenants; +import net.jpountz.xxhash.XXHash64; +import net.jpountz.xxhash.XXHashFactory; +import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -68,6 +76,18 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { static final int TRACELEVEL_DEBUG = 9; private static final String THREADPOOL_NAME = "rpcserver worker pool"; private static final long SHUTDOWN_TIMEOUT = 60; + private enum FileApiErrorCodes { + OK(0, "OK"), + NOT_FOUND(1, "Filereference not found"); + private final int code; + private final String description; + FileApiErrorCodes(int code, String description) { + this.code = code; + this.description = description; + } + int getCode() { return code; } + String getDescription() { return description; } + } private final Supervisor supervisor = new Supervisor(new Transport()); private Spec spec = null; private final boolean useRequestVersion; @@ -83,6 +103,7 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { private final MetricUpdater metrics; private final MetricUpdaterFactory metricUpdaterFactory; private final HostLivenessTracker hostLivenessTracker; + private final FileServer fileServer; private final ThreadPoolExecutor executorService; private volatile boolean allTenantsLoaded = false; @@ -93,20 +114,23 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { * @param config The config to use for setting up this server */ @Inject - public RpcServer(ConfigserverConfig config, SuperModelRequestHandler superModelRequestHandler, MetricUpdaterFactory metrics, - HostRegistries hostRegistries, HostLivenessTracker hostLivenessTracker) { + public RpcServer(ConfigserverConfig config, SuperModelRequestHandler superModelRequestHandler, + MetricUpdaterFactory metrics, HostRegistries hostRegistries, + HostLivenessTracker hostLivenessTracker, FileServer fileServer) { this.superModelRequestHandler = superModelRequestHandler; - this.metricUpdaterFactory = metrics; - this.supervisor.setMaxOutputBufferSize(config.maxoutputbuffersize()); + metricUpdaterFactory = metrics; + supervisor.setMaxOutputBufferSize(config.maxoutputbuffersize()); this.metrics = metrics.getOrCreateMetricUpdater(Collections.<String, String>emptyMap()); this.hostLivenessTracker = hostLivenessTracker; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(config.maxgetconfigclients()); - executorService = new ThreadPoolExecutor(config.numthreads(), config.numthreads(), 0, TimeUnit.SECONDS, workQueue, ThreadFactoryFactory.getThreadFactory(THREADPOOL_NAME)); + executorService = new ThreadPoolExecutor(config.numthreads(), config.numthreads(), + 0, TimeUnit.SECONDS, workQueue, ThreadFactoryFactory.getThreadFactory(THREADPOOL_NAME)); delayedConfigResponses = new DelayedConfigResponses(this, config.numDelayedResponseThreads()); spec = new Spec(null, config.rpcport()); hostRegistry = hostRegistries.getTenantHostRegistry(); this.useRequestVersion = config.useVespaVersionInRequest(); this.hostedVespa = config.hostedVespa(); + this.fileServer = fileServer; setUpHandlers(); } @@ -180,6 +204,7 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { getSupervisor().addMethod(new Method("printStatistics", "", "s", this, "printStatistics") .methodDesc("printStatistics") .returnDesc(0, "statistics", "Statistics for server")); + getSupervisor().addMethod(new Method("filedistribution.serveFile", "s", "is", this, "serveFile")); } /** @@ -223,7 +248,6 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { } for (int i = 0; i < responsesSent; i++) { - try { completionService.take(); } catch (InterruptedException e) { @@ -402,4 +426,56 @@ public class RpcServer implements Runnable, ReloadListener, TenantListener { return useRequestVersion; } + class FileReceiver implements FileServer.Receiver { + Target target; + FileReceiver(Target target) { + this.target = target; + } + + @Override + public String toString() { + return target.toString(); + } + + @Override + public void receive(FileReference reference, String filename, byte [] content, FileServer.ReplayStatus status) { + XXHash64 hasher = XXHashFactory.fastestInstance().hash64(); + Request fileBlob = new Request("filedistribution.receiveFile"); + fileBlob.parameters().add(new StringValue(reference.value())); + fileBlob.parameters().add(new StringValue(filename)); + fileBlob.parameters().add(new DataValue(content)); + fileBlob.parameters().add(new Int64Value(hasher.hash(ByteBuffer.wrap(content), 0))); + fileBlob.parameters().add(new Int32Value(status.getCode())); + fileBlob.parameters().add(new StringValue(status.getDescription())); + target.invokeSync(fileBlob, 600); + if (fileBlob.isError()) { + log.warning("Failed delivering reference '" + reference.value() + "' with file '" + filename + "' to " + + target.toString() + " with error : '" + fileBlob.errorMessage() + "'."); + } + } + } + + @SuppressWarnings("UnusedDeclaration") + public final void serveFile(Request request) { + String fileReference = request.parameters().get(0).asString(); + FileApiErrorCodes result; + try { + // TODO remove once verified in system tests. + log.info("Received request for reference '" + fileReference + "'"); + result = fileServer.hasFile(fileReference) + ? FileApiErrorCodes.OK + : FileApiErrorCodes.NOT_FOUND; + if (result == FileApiErrorCodes.OK) { + fileServer.startFileServing(fileReference, new FileReceiver(request.target())); + } else { + fileServer.download(new FileReference(fileReference)); + } + } catch (IllegalArgumentException e) { + result = FileApiErrorCodes.NOT_FOUND; + log.warning("Failed serving file reference '" + fileReference + "' with error " + e.toString()); + } + request.returnValues() + .add(new Int32Value(result.getCode())) + .add(new StringValue(result.getDescription())); + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java index 298acaca901..e96ddb4b094 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java @@ -6,6 +6,7 @@ import com.yahoo.path.Path; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.vespa.config.server.GlobalComponentRegistry; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.curator.Curator; @@ -19,20 +20,19 @@ public class RemoteSessionFactory { private final GlobalComponentRegistry componentRegistry; private final Curator curator; private final ConfigCurator configCurator; - private final Path sessionDirPath; + private final Path sessionsPath; private final ConfigDefinitionRepo defRepo; private final TenantName tenant; private final ConfigserverConfig configserverConfig; private final Clock clock; public RemoteSessionFactory(GlobalComponentRegistry componentRegistry, - Path sessionsPath, TenantName tenant, Clock clock) { this.componentRegistry = componentRegistry; this.curator = componentRegistry.getCurator(); this.configCurator = componentRegistry.getConfigCurator(); - this.sessionDirPath = sessionsPath; + this.sessionsPath = Tenants.getSessionsPath(tenant); this.tenant = tenant; this.defRepo = componentRegistry.getConfigDefinitionRepo(); this.configserverConfig = componentRegistry.getConfigserverConfig(); @@ -40,7 +40,7 @@ public class RemoteSessionFactory { } public RemoteSession createSession(long sessionId) { - Path sessionPath = sessionDirPath.append(String.valueOf(sessionId)); + Path sessionPath = this.sessionsPath.append(String.valueOf(sessionId)); SessionZooKeeperClient sessionZKClient = new SessionZooKeeperClient(curator, configCurator, sessionPath, diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java index 659a44bb339..2269a7ed997 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionRepo.java @@ -8,10 +8,12 @@ import java.util.logging.Logger; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; +import com.yahoo.config.provision.TenantName; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.config.server.application.ApplicationSet; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; import com.yahoo.yolean.Exceptions; import com.yahoo.vespa.config.server.ReloadHandler; @@ -49,19 +51,19 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> implements Nod * @param curator a {@link Curator} instance. * @param remoteSessionFactory a {@link com.yahoo.vespa.config.server.session.RemoteSessionFactory} * @param reloadHandler a {@link com.yahoo.vespa.config.server.ReloadHandler} - * @param sessionsPath a {@link com.yahoo.path.Path} to the sessions dir. - * @param applicationRepo an {@link TenantApplications} object. + * @param tenant a {@link TenantName} instance. + * @param applicationRepo a {@link TenantApplications} instance. * @param executorService an {@link ExecutorService} to run callbacks from ZooKeeper. */ public RemoteSessionRepo(Curator curator, - RemoteSessionFactory remoteSessionFactory, - ReloadHandler reloadHandler, - Path sessionsPath, - TenantApplications applicationRepo, - MetricUpdater metricUpdater, - ExecutorService executorService) { + RemoteSessionFactory remoteSessionFactory, + ReloadHandler reloadHandler, + TenantName tenant, + TenantApplications applicationRepo, + MetricUpdater metricUpdater, + ExecutorService executorService) { this.curator = curator; - this.sessionsPath = sessionsPath; + this.sessionsPath = Tenants.getSessionsPath(tenant); this.applicationRepo = applicationRepo; this.remoteSessionFactory = remoteSessionFactory; this.reloadHandler = reloadHandler; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java index 1d5025f2e61..fdc681b5fb6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java @@ -16,6 +16,7 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.host.HostValidator; +import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.config.server.zookeeper.SessionCounter; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; @@ -57,7 +58,6 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { public SessionFactoryImpl(GlobalComponentRegistry globalComponentRegistry, SessionCounter sessionCounter, - Path sessionsPath, TenantApplications applicationRepo, TenantFileSystemDirs tenantFileSystemDirs, HostValidator<ApplicationId> hostRegistry, @@ -68,7 +68,7 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { this.curator = globalComponentRegistry.getCurator(); this.configCurator = globalComponentRegistry.getConfigCurator(); this.sessionCounter = sessionCounter; - this.sessionsPath = sessionsPath; + this.sessionsPath = Tenants.getSessionsPath(tenant); this.applicationRepo = applicationRepo; this.tenantFileSystemDirs = tenantFileSystemDirs; this.superModelGenerationCounter = globalComponentRegistry.getSuperModelGenerationCounter(); 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 31be18d9b22..531085883c4 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 @@ -9,25 +9,32 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.application.api.FileRegistry; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.ModelContext; -import com.yahoo.config.provision.*; +import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.OutOfCapacityException; +import com.yahoo.config.provision.Rotation; +import com.yahoo.config.provision.Version; +import com.yahoo.config.provision.Zone; import com.yahoo.lang.SettableOptional; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; -import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.ConfigServerSpec; +import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; -import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; -import com.yahoo.vespa.config.server.tenant.Rotations; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.deploy.ModelContextImpl; import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer; import com.yahoo.vespa.config.server.http.InvalidApplicationException; +import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; import com.yahoo.vespa.config.server.modelfactory.PreparedModelsBuilder; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; - +import com.yahoo.vespa.config.server.tenant.Rotations; import com.yahoo.vespa.curator.Curator; import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; import java.io.IOException; import java.time.Instant; import java.util.List; @@ -37,9 +44,6 @@ import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; - /** * A SessionPreparer is responsible for preparing a session given an application package. * @@ -148,6 +152,7 @@ public class SessionPreparer { this.properties = new ModelContextImpl.Properties(params.getApplicationId(), configserverConfig.multitenant(), ConfigServerSpec.fromConfig(configserverConfig), + HostName.from(configserverConfig.loadBalancerAddress()), configserverConfig.hostedVespa(), zone, rotationsSet); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java index 084d35a42d4..61145c2a138 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java @@ -34,7 +34,6 @@ public class TenantBuilder { private final Path tenantPath; private final GlobalComponentRegistry componentRegistry; private final TenantName tenant; - private final Path sessionsPath; private RemoteSessionRepo remoteSessionRepo; private LocalSessionRepo localSessionRepo; private SessionFactory sessionFactory; @@ -47,15 +46,14 @@ public class TenantBuilder { private TenantFileSystemDirs tenantFileSystemDirs; private HostValidator<ApplicationId> hostValidator; - private TenantBuilder(GlobalComponentRegistry componentRegistry, TenantName tenant, Path zkPath) { + private TenantBuilder(GlobalComponentRegistry componentRegistry, TenantName tenant) { this.componentRegistry = componentRegistry; - this.tenantPath = zkPath; + this.tenantPath = Tenants.getTenantPath(tenant); this.tenant = tenant; - this.sessionsPath = tenantPath.append(Tenant.SESSIONS); } - public static TenantBuilder create(GlobalComponentRegistry componentRegistry, TenantName tenant, Path zkPath) { - return new TenantBuilder(componentRegistry, tenant, zkPath); + public static TenantBuilder create(GlobalComponentRegistry componentRegistry, TenantName tenant) { + return new TenantBuilder(componentRegistry, tenant); } public TenantBuilder withSessionFactory(SessionFactory sessionFactory) { @@ -123,7 +121,7 @@ public class TenantBuilder { private void createSessionFactory() { if (sessionFactory == null || localSessionLoader == null) { - SessionFactoryImpl impl = new SessionFactoryImpl(componentRegistry, sessionCounter, sessionsPath, + SessionFactoryImpl impl = new SessionFactoryImpl(componentRegistry, sessionCounter, applicationRepo, tenantFileSystemDirs, hostValidator, tenant); if (sessionFactory == null) { sessionFactory = impl; @@ -136,13 +134,13 @@ public class TenantBuilder { private void createApplicationRepo() { if (applicationRepo == null) { - applicationRepo = ZKTenantApplications.create(componentRegistry.getCurator(), tenantPath.append(Tenant.APPLICATIONS), reloadHandler, tenant); + applicationRepo = ZKTenantApplications.create(componentRegistry.getCurator(), reloadHandler, tenant); } } private void createSessionCounter() { if (sessionCounter == null) { - sessionCounter = new SessionCounter(componentRegistry.getCurator(), tenantPath, sessionsPath); + sessionCounter = new SessionCounter(componentRegistry.getCurator(), tenant); } } @@ -167,7 +165,7 @@ public class TenantBuilder { private void createRemoteSessionFactory(Clock clock) { if (remoteSessionFactory == null) { - remoteSessionFactory = new RemoteSessionFactory(componentRegistry, sessionsPath, tenant, clock); + remoteSessionFactory = new RemoteSessionFactory(componentRegistry, tenant, clock); } } @@ -176,7 +174,7 @@ public class TenantBuilder { remoteSessionRepo = new RemoteSessionRepo(componentRegistry.getCurator(), remoteSessionFactory, reloadHandler, - sessionsPath, + tenant, applicationRepo, componentRegistry.getMetrics().getOrCreateMetricUpdater(Metrics.createDimensions(tenant)), createSingleThreadedExecutorService(RemoteSessionRepo.class.getName())); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java index 60200e34cdf..d2cf17a38d4 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java @@ -11,7 +11,6 @@ import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; -import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.curator.Curator; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; @@ -78,10 +77,10 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen * @throws Exception is creating the Tenants instance fails */ @Inject - public Tenants(GlobalComponentRegistry globalComponentRegistry, Metrics metrics) throws Exception { + public Tenants(GlobalComponentRegistry globalComponentRegistry) throws Exception { this.globalComponentRegistry = globalComponentRegistry; this.curator = globalComponentRegistry.getCurator(); - metricUpdater = metrics.getOrCreateMetricUpdater(Collections.emptyMap()); + metricUpdater = globalComponentRegistry.getMetrics().getOrCreateMetricUpdater(Collections.emptyMap()); this.tenantListeners.add(globalComponentRegistry.getTenantListener()); curator.framework().getConnectionStateListenable().addListener(this); @@ -99,15 +98,16 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen /** * New instance containing the given tenants. This will not create Zookeeper watches. For testing only * @param globalComponentRegistry a {@link com.yahoo.vespa.config.server.GlobalComponentRegistry} instance - * @param metrics a {@link com.yahoo.vespa.config.server.monitoring.Metrics} instance * @param tenants a collection of {@link Tenant}s */ - public Tenants(GlobalComponentRegistry globalComponentRegistry, Metrics metrics, Collection<Tenant> tenants) { + public Tenants(GlobalComponentRegistry globalComponentRegistry, Collection<Tenant> tenants) { this.globalComponentRegistry = globalComponentRegistry; this.curator = globalComponentRegistry.getCurator(); - metricUpdater = metrics.getOrCreateMetricUpdater(Collections.emptyMap()); + metricUpdater = globalComponentRegistry.getMetrics().getOrCreateMetricUpdater(Collections.emptyMap()); this.tenantListeners.add(globalComponentRegistry.getTenantListener()); curator.create(tenantsPath); + createSystemTenants(globalComponentRegistry.getConfigserverConfig()); + createTenants(); this.directoryCache = curator.createDirectoryCache(tenantsPath.getAbsolute(), false, false, pathChildrenExecutor); this.tenants.putAll(addTenants(tenants)); } @@ -147,7 +147,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen return tenants; } - synchronized void createTenants() throws Exception { + synchronized void createTenants() { Set<TenantName> allTenants = readTenantsFromZooKeeper(); log.log(LogLevel.DEBUG, "Create tenants, tenants found in zookeeper: " + allTenants); checkForRemovedTenants(allTenants); @@ -159,7 +159,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen Map<TenantName, Tenant> current = new LinkedHashMap<>(tenants); for (Map.Entry<TenantName, Tenant> entry : current.entrySet()) { TenantName tenant = entry.getKey(); - if (!newTenants.contains(tenant)) { + if (!newTenants.contains(tenant) && !DEFAULT_TENANT.equals(tenant)) { notifyRemovedTenant(tenant); entry.getValue().close(); tenants.remove(tenant); @@ -167,7 +167,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen } } - private void checkForAddedTenants(Set<TenantName> newTenants) throws Exception { + private void checkForAddedTenants(Set<TenantName> newTenants) { ExecutorService executor = Executors.newFixedThreadPool(globalComponentRegistry.getConfigserverConfig().numParallelTenantLoaders()); for (TenantName tenantName : newTenants) { // Note: the http handler will check if the tenant exists, and throw accordingly @@ -178,12 +178,18 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen } } executor.shutdown(); - executor.awaitTermination(365, TimeUnit.DAYS); // Timeout should never happen + try { + executor.awaitTermination(365, TimeUnit.DAYS); // Timeout should never happen + } catch (InterruptedException e) { + throw new RuntimeException("Executor for creating tenants did not terminate within timeout"); + } } private void createTenant(TenantName tenantName) { + if (tenants.containsKey(tenantName)) return; + try { - Tenant tenant = TenantBuilder.create(globalComponentRegistry, tenantName, getTenantPath(tenantName)).build(); + Tenant tenant = TenantBuilder.create(globalComponentRegistry, tenantName).build(); notifyNewTenant(tenant); tenants.put(tenantName, tenant); } catch (Exception e) { @@ -251,6 +257,8 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen * @return this Tenants instance */ public synchronized Tenants deleteTenant(TenantName name) { + if (name.equals(DEFAULT_TENANT)) + throw new IllegalArgumentException("Deleting 'default' tenant is not allowed"); Tenant tenant = tenants.get(name); tenant.delete(); return this; @@ -267,7 +275,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen * @return the log string */ public static String logPre(ApplicationId app) { - if (TenantName.defaultName().equals(app.tenant())) return ""; + if (DEFAULT_TENANT.equals(app.tenant())) return ""; StringBuilder ret = new StringBuilder() .append(logPre(app.tenant())) .append("app:"+app.application().value()) @@ -343,6 +351,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen /** * Gets zookeeper path for tenant data + * * @param tenantName tenant name * @return a {@link com.yahoo.path.Path} to the zookeeper data for a tenant */ @@ -350,4 +359,24 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen return tenantsPath.append(tenantName.value()); } + /** + * Gets zookeeper path for session data for a tenant + * + * @param tenantName tenant name + * @return a {@link com.yahoo.path.Path} to the zookeeper sessions data for a tenant + */ + public static Path getSessionsPath(TenantName tenantName) { + return getTenantPath(tenantName).append(Tenant.SESSIONS); + } + + /** + * Gets zookeeper path for application data for a tenant + * + * @param tenantName tenant name + * @return a {@link com.yahoo.path.Path} to the zookeeper application data for a tenant + */ + public static Path getApplicationsPath(TenantName tenantName) { + return getTenantPath(tenantName).append(Tenant.APPLICATIONS); + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/SessionCounter.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/SessionCounter.java index 2d95a013da9..4df292dd204 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/SessionCounter.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/SessionCounter.java @@ -1,7 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.zookeeper; -import com.yahoo.path.Path; +import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; /** @@ -12,8 +13,10 @@ import com.yahoo.vespa.curator.Curator; */ public class SessionCounter extends InitializedCounter { - public SessionCounter(Curator curator, Path rootPath, Path sessionsDir) { - super(curator, rootPath.append("sessionCounter").getAbsolute(), sessionsDir.getAbsolute()); + public SessionCounter(Curator curator, TenantName tenantName) { + super(curator, + Tenants.getTenantPath(tenantName).append("sessionCounter").getAbsolute(), + Tenants.getSessionsPath(tenantName).getAbsolute()); } /** diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml index 7ad1b3bbbfd..fbab854ae9e 100644 --- a/configserver/src/main/resources/configserver-app/services.xml +++ b/configserver/src/main/resources/configserver-app/services.xml @@ -1,13 +1,13 @@ <?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"> - <preprocess:include file='controller/admin.xml' required='false' /> <jdisc id="configserver" jetty="true" version="1.0"> <config name="container.handler.threadpool"> <maxthreads>100</maxthreads> <!-- Reduced thread count to minimize memory consumption --> </config> <accesslog type="vespa" fileNamePattern="logs/vespa/configserver/access.log.%Y%m%d%H%M%S" rotationScheme="date" symlinkName="access.log" /> + <preprocess:include file='access-logging.xml' required='false' /> <component id="com.yahoo.vespa.config.server.ConfigServerBootstrap" bundle="configserver" /> <component id="com.yahoo.vespa.config.server.monitoring.Metrics" bundle="configserver" /> <component id="com.yahoo.vespa.zookeeper.ZooKeeperServer" bundle="zkfacade" /> @@ -34,6 +34,7 @@ <component id="com.yahoo.config.provision.Zone" bundle="config-provisioning" /> <component id="com.yahoo.vespa.config.server.application.ApplicationConvergenceChecker" bundle="configserver" /> <component id="com.yahoo.vespa.config.server.application.HttpProxy" bundle="configserver" /> + <component id="com.yahoo.vespa.config.server.filedistribution.FileServer" bundle="configserver" /> <component id="com.yahoo.vespa.serviceview.ConfigServerLocation" bundle="configserver" /> @@ -44,6 +45,7 @@ <preprocess:include file='config-models.xml' required='false' /> <preprocess:include file='node-repository.xml' required='false' /> <preprocess:include file='hosted-vespa/routing-status.xml' required='false' /> + <preprocess:include file='hosted-vespa/scoreboard.xml' required='false' /> <preprocess:include file='controller/container.xml' required='false' /> <component id="com.yahoo.vespa.service.monitor.internal.SlobrokMonitorManagerImpl" bundle="service-monitor" /> <component id="com.yahoo.vespa.service.monitor.internal.ServiceMonitorImpl" bundle="service-monitor" /> diff --git a/configserver/src/main/sh/vespa-configserver-remove-state b/configserver/src/main/sh/vespa-configserver-remove-state index 404f0f89a53..c781fcb0c0d 100755 --- a/configserver/src/main/sh/vespa-configserver-remove-state +++ b/configserver/src/main/sh/vespa-configserver-remove-state @@ -75,14 +75,12 @@ usage() { sudo="sudo" ask=true remove_zookeeper_dir=true -remove_applications_dir=true remove_tenants_dir=true confirmed=true zookeeper_dir=var/zookeeper -applications_dir=var/db/vespa/config_server/serverdb/applications tenants_dir=var/db/vespa/config_server/serverdb/tenants -if [ -w $applications_dir ] && [ -w $zookeeper_dir ]; then +if [ -w $zookeeper_dir ]; then sudo="" fi @@ -123,9 +121,8 @@ confirm() { } garbage_collect_dirs() { - find $zookeeper_dir $applications_dir -type d -depth 2>/dev/null | while read dir; do + find $zookeeper_dir $tenants_dir -type d -depth 2>/dev/null | while read dir; do [ "$dir" = "$zookeeper_dir" ] && continue - [ "$dir" = "$applications_dir" ] && continue $sudo rmdir "$dir" 2>/dev/null done } @@ -148,10 +145,6 @@ if $remove_zookeeper_dir && [ -d $zookeeper_dir ]; then confirm_and_clean_dir $zookeeper_dir fi -if $remove_applications_dir && [ -d $applications_dir ]; then - confirm_and_clean_dir $applications_dir -fi - if $remove_tenants_dir && [ -d $tenants_dir ]; then confirm_and_clean_dir $tenants_dir fi diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java index 3efad7ac133..dec9dd991de 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java @@ -5,8 +5,10 @@ import com.google.common.io.Files; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.NullConfigModelRegistry; import com.yahoo.config.model.api.ConfigDefinitionRepo; +import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; +import com.yahoo.vespa.config.server.filedistribution.FileServer; import com.yahoo.vespa.config.server.host.ConfigRequestHostLivenessTracker; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.http.SessionHandlerTest; @@ -61,7 +63,7 @@ public class InjectedGlobalComponentRegistryTest { serverDB = new ConfigServerDB(configserverConfig); sessionPreparer = new SessionTest.MockSessionPreparer(); rpcServer = new RpcServer(configserverConfig, null, Metrics.createTestMetrics(), - new HostRegistries(), new ConfigRequestHostLivenessTracker()); + new HostRegistries(), new ConfigRequestHostLivenessTracker(), new FileServer(FileDistribution.getDefaultFileDBPath())); generationCounter = new SuperModelGenerationCounter(curator); defRepo = new StaticConfigDefinitionRepo(); permanentApplicationPackage = new PermanentApplicationPackage(configserverConfig); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java index b53f82b25f3..aed0a6a9750 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java @@ -45,6 +45,7 @@ public class ModelContextImplTest { ApplicationId.defaultId(), true, Collections.emptyList(), + null, false, Zone.defaultZone(), rotations), diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java index 1a14ac1761c..08cfa74da3b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java @@ -3,11 +3,11 @@ package com.yahoo.vespa.config.server.application; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; -import com.yahoo.path.Path; import com.yahoo.text.Utf8; import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.TestWithCurator; +import com.yahoo.vespa.config.server.tenant.Tenants; import org.junit.Test; import java.util.Arrays; @@ -18,60 +18,61 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; /** - * @author lulf + * @author Ulf Lilleengen * @since 5.1 */ public class TenantApplicationsTest extends TestWithCurator { + private static final TenantName tenantName = TenantName.from("tenant"); + @Test public void require_that_applications_are_read_from_zookeeper() throws Exception { - curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz", Utf8.toAsciiBytes(3)); - curatorFramework.create().creatingParentsIfNeeded().forPath("/bar:test:bim", Utf8.toAsciiBytes(4)); + writeApplicationData(createApplicationId("foo"), 3L); + writeApplicationData(createApplicationId("bar"), 4L); TenantApplications repo = createZKAppRepo(); List<ApplicationId> applications = repo.listApplications(); assertThat(applications.size(), is(2)); - assertThat(applications.get(0).application().value(), is("dev")); - assertThat(applications.get(1).application().value(), is("test")); + assertThat(applications.get(0).application().value(), is("foo")); + assertThat(applications.get(1).application().value(), is("bar")); assertThat(repo.getSessionIdForApplication(applications.get(0)), is(3L)); assertThat(repo.getSessionIdForApplication(applications.get(1)), is(4L)); } @Test public void require_that_invalid_entries_are_skipped() throws Exception { - curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz"); - curatorFramework.create().creatingParentsIfNeeded().forPath("/invalid"); + writeApplicationData(createApplicationId("foo"), 3L); + writeApplicationData("invalid", 3L); TenantApplications repo = createZKAppRepo(); List<ApplicationId> applications = repo.listApplications(); assertThat(applications.size(), is(1)); - assertThat(applications.get(0).application().value(), is("dev")); + assertThat(applications.get(0).application().value(), is("foo")); } @Test(expected = IllegalArgumentException.class) public void require_that_requesting_session_for_unknown_application_throws_exception() throws Exception { - curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz:bim"); TenantApplications repo = createZKAppRepo(); - repo.getSessionIdForApplication(new ApplicationId.Builder() - .tenant("exist") - .applicationName("tenant").instanceName("here").build()); + repo.getSessionIdForApplication(createApplicationId("nonexistent")); } @Test(expected = IllegalArgumentException.class) public void require_that_requesting_session_for_empty_application_throws_exception() throws Exception { - curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz:bim"); + ApplicationId baz = createApplicationId("baz"); + // No data in node + curatorFramework.create().creatingParentsIfNeeded() + .forPath(Tenants.getApplicationsPath(tenantName).append(baz.serializedForm()).getAbsolute()); TenantApplications repo = createZKAppRepo(); - repo.getSessionIdForApplication(new ApplicationId.Builder() - .tenant("tenant") - .applicationName("foo").instanceName("bim").build()); + repo.getSessionIdForApplication(baz); } @Test public void require_that_application_ids_can_be_written() throws Exception { TenantApplications repo = createZKAppRepo(); - repo.createPutApplicationTransaction(createAppplicationId("myapp"), 3l).commit(); - String path = "/mytenant:myapp:myinst"; + ApplicationId myapp = createApplicationId("myapp"); + repo.createPutApplicationTransaction(myapp, 3l).commit(); + String path = Tenants.getApplicationsPath(tenantName).append(myapp.serializedForm()).getAbsolute(); assertTrue(curatorFramework.checkExists().forPath(path) != null); assertThat(Utf8.toString(curatorFramework.getData().forPath(path)), is("3")); - repo.createPutApplicationTransaction(createAppplicationId("myapp"), 5l).commit(); + repo.createPutApplicationTransaction(myapp, 5l).commit(); assertTrue(curatorFramework.checkExists().forPath(path) != null); assertThat(Utf8.toString(curatorFramework.getData().forPath(path)), is("5")); } @@ -79,8 +80,8 @@ public class TenantApplicationsTest extends TestWithCurator { @Test public void require_that_application_ids_can_be_deleted() throws Exception { TenantApplications repo = createZKAppRepo(); - ApplicationId id1 = createAppplicationId("myapp"); - ApplicationId id2 = createAppplicationId("myapp2"); + ApplicationId id1 = createApplicationId("myapp"); + ApplicationId id2 = createApplicationId("myapp2"); repo.createPutApplicationTransaction(id1, 1).commit(); repo.createPutApplicationTransaction(id2, 1).commit(); assertThat(repo.listApplications().size(), is(2)); @@ -95,8 +96,8 @@ public class TenantApplicationsTest extends TestWithCurator { TenantApplications zkRepo = createZKAppRepo(); TenantApplications memRepo = new MemoryTenantApplications(); for (TenantApplications repo : Arrays.asList(zkRepo, memRepo)) { - ApplicationId id1 = createAppplicationId("myapp"); - ApplicationId id2 = createAppplicationId("myapp2"); + ApplicationId id1 = createApplicationId("myapp"); + ApplicationId id2 = createApplicationId("myapp2"); repo.createPutApplicationTransaction(id1, 4).commit(); repo.createPutApplicationTransaction(id2, 5).commit(); List<ApplicationId> lst = repo.listApplications(); @@ -122,21 +123,19 @@ public class TenantApplicationsTest extends TestWithCurator { @Test public void require_that_reload_handler_is_called_when_apps_are_removed() throws Exception { - curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:test:baz", Utf8.toAsciiBytes(3)); - curatorFramework.create().creatingParentsIfNeeded().forPath("/bar:dev:bim", Utf8.toAsciiBytes(4)); + ApplicationId foo = createApplicationId("foo"); + writeApplicationData(foo, 3L); + writeApplicationData(createApplicationId("bar"), 4L); MockReloadHandler reloadHandler = new MockReloadHandler(); TenantApplications repo = createZKAppRepo(reloadHandler); assertNull(reloadHandler.lastRemoved); - repo.deleteApplication(new ApplicationId.Builder() - .tenant("foo") - .applicationName("test").instanceName("baz").build()) - .commit(); + repo.deleteApplication(foo).commit(); long endTime = System.currentTimeMillis() + 60_000; while (System.currentTimeMillis() < endTime && reloadHandler.lastRemoved == null) { Thread.sleep(100); } assertNotNull(reloadHandler.lastRemoved); - assertThat(reloadHandler.lastRemoved.serializedForm(), is("foo:test:baz")); + assertThat(reloadHandler.lastRemoved.serializedForm(), is(foo.serializedForm())); } private TenantApplications createZKAppRepo() { @@ -144,12 +143,26 @@ public class TenantApplicationsTest extends TestWithCurator { } private TenantApplications createZKAppRepo(MockReloadHandler reloadHandler) { - return ZKTenantApplications.create(curator, Path.createRoot(), reloadHandler, TenantName.from("mytenant")); + return ZKTenantApplications.create(curator, reloadHandler, tenantName); } - private static ApplicationId createAppplicationId(String name) { + private static ApplicationId createApplicationId(String name) { return new ApplicationId.Builder() - .tenant("mytenant") - .applicationName(name).instanceName("myinst").build(); + .tenant(tenantName.value()) + .applicationName(name) + .instanceName("myinst") + .build(); + } + + private void writeApplicationData(ApplicationId applicationId, long sessionId) throws Exception { + writeApplicationData(applicationId.serializedForm(), sessionId); + } + + private void writeApplicationData(String applicationId, long sessionId) throws Exception { + curatorFramework + .create() + .creatingParentsIfNeeded() + .forPath(Tenants.getApplicationsPath(tenantName).append(applicationId).getAbsolute(), + Utf8.toAsciiBytes(sessionId)); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java index 64932700173..d9a0db7e811 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -69,26 +69,19 @@ public class DeployTester { } public DeployTester(String appPath, List<ModelFactory> modelFactories) { - this(appPath, modelFactories, new ConfigserverConfig(new ConfigserverConfig.Builder() - .configServerDBDir(Files.createTempDir() - .getAbsolutePath()) - .configDefinitionsDir(Files.createTempDir() - .getAbsolutePath())), + this(appPath, modelFactories, + new ConfigserverConfig(new ConfigserverConfig.Builder() + .configServerDBDir(Files.createTempDir().getAbsolutePath()) + .configDefinitionsDir(Files.createTempDir().getAbsolutePath())), Clock.systemUTC()); } public DeployTester(String appPath, ConfigserverConfig configserverConfig) { - this(appPath, - Collections.singletonList(createModelFactory(Clock.systemUTC())), - configserverConfig, - Clock.systemUTC()); + this(appPath, Collections.singletonList(createModelFactory(Clock.systemUTC())), configserverConfig, Clock.systemUTC()); } public DeployTester(String appPath, ConfigserverConfig configserverConfig, Clock clock) { - this(appPath, - Collections.singletonList(createModelFactory(clock)), - configserverConfig, - clock); + this(appPath, Collections.singletonList(createModelFactory(clock)), configserverConfig, clock); } public DeployTester(String appPath, List<ModelFactory> modelFactories, ConfigserverConfig configserverConfig) { @@ -96,24 +89,22 @@ public class DeployTester { } public DeployTester(String appPath, List<ModelFactory> modelFactories, ConfigserverConfig configserverConfig, Clock clock) { - Metrics metrics = Metrics.createTestMetrics(); - Curator curator = new MockCurator(); this.clock = clock; - TestComponentRegistry componentRegistry = createComponentRegistry(curator, metrics, modelFactories, - configserverConfig, clock); + TestComponentRegistry componentRegistry = createComponentRegistry(new MockCurator(), Metrics.createTestMetrics(), + modelFactories, configserverConfig, clock); try { this.testApp = new File(appPath); - this.tenants = new Tenants(componentRegistry, metrics); + this.tenants = new Tenants(componentRegistry, Collections.emptySet()); } catch (Exception e) { throw new IllegalArgumentException(e); } - applicationRepository = new ApplicationRepository(tenants, - createHostProvisioner(), - clock); + applicationRepository = new ApplicationRepository(tenants, createHostProvisioner(), clock); } - public Tenant tenant() { return tenants.defaultTenant(); } + public Tenant tenant() { + return tenants.defaultTenant(); + } /** Create a model factory for the version of this source*/ public static ModelFactory createModelFactory(Clock clock) { @@ -139,6 +130,7 @@ public class DeployTester { * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. */ public ApplicationId deployApp(String appName, String vespaVersion, Instant now) { + Tenant tenant = tenant(); LocalSession session = tenant.getSessionFactory().createSession(testApp, appName, new TimeoutBudget(clock, Duration.ofSeconds(60))); ApplicationId id = ApplicationId.from(tenant.getName(), ApplicationName.from(appName), InstanceName.defaultName()); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java new file mode 100644 index 00000000000..09260987ac0 --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileServerTest.java @@ -0,0 +1,112 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.filedistribution; + +import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.FileReference; +import com.yahoo.io.IOUtils; +import com.yahoo.net.HostName; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +public class FileServerTest { + + FileServer fs = new FileServer(new File(".")); + List<File> created = new LinkedList<>(); + + private void createCleanDir(String name) throws IOException{ + File dir = new File(name); + IOUtils.recursiveDeleteDir(dir); + IOUtils.createDirectory(dir.getName()); + File dummy = new File(dir.getName() +"/dummy"); + IOUtils.writeFile(dummy, "test", true); + assertTrue(dummy.delete()); + created.add(dir); + } + + @Test + public void requireThatExistingFileCanbeFound() throws IOException { + createCleanDir("123"); + IOUtils.writeFile("123/f1", "test", true); + assertTrue(fs.hasFile("123")); + cleanup(); + } + + @Test + public void requireThatNonExistingFileCanNotBeFound() throws IOException { + assertFalse(fs.hasFile("12x")); + createCleanDir("12x"); + assertFalse(fs.hasFile("12x")); + cleanup(); + } + + private static class FileReceiver implements FileServer.Receiver { + CompletableFuture<byte []> content; + FileReceiver(CompletableFuture<byte []> content) { + this.content = content; + } + @Override + public void receive(FileReference reference, String filename, byte[] content, FileServer.ReplayStatus status) { + this.content.complete(content); + } + } + + @Test + public void requireThatWeCanReplayFile() throws IOException, InterruptedException, ExecutionException { + createCleanDir("12y"); + IOUtils.writeFile("12y/f1", "dummy-data", true); + CompletableFuture<byte []> content = new CompletableFuture<>(); + fs.startFileServing("12y", new FileReceiver(content)); + assertEquals(new String(content.get()), "dummy-data"); + cleanup(); + } + + @Test + public void requireThatDifferentNumberOfConfigServersWork() throws IOException { + // Empty connection pool in tests etc. + ConfigserverConfig.Builder builder = new ConfigserverConfig.Builder(); + FileServer fileServer = new FileServer(new ConfigserverConfig(builder)); + assertEquals(0, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize()); + + // Empty connection pool when only one server, no use in downloading from yourself + List<ConfigserverConfig.Zookeeperserver.Builder> servers = new ArrayList<>(); + ConfigserverConfig.Zookeeperserver.Builder serverBuilder = new ConfigserverConfig.Zookeeperserver.Builder(); + serverBuilder.hostname(HostName.getLocalhost()); + serverBuilder.port(123456); + servers.add(serverBuilder); + builder.zookeeperserver(servers); + fileServer = new FileServer(new ConfigserverConfig(builder)); + assertEquals(0, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize()); + + // connection pool of size 1 when 2 servers + ConfigserverConfig.Zookeeperserver.Builder serverBuilder2 = new ConfigserverConfig.Zookeeperserver.Builder(); + serverBuilder2.hostname("bar"); + serverBuilder2.port(123456); + servers.add(serverBuilder2); + builder.zookeeperserver(servers); + fileServer = new FileServer(new ConfigserverConfig(builder)); + assertEquals(1, fileServer.downloader().fileReferenceDownloader().connectionPool().getSize()); + } + + private void cleanup() { + created.forEach((file) -> IOUtils.recursiveDeleteDir(file)); + created.clear(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + cleanup(); + } + +} diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStreamTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStreamTest.java index 5d23f1a4556..ddd29f96695 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStreamTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStreamTest.java @@ -1,8 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.http; +import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; -import com.yahoo.vespa.config.server.http.CompressedApplicationInputStream; import org.apache.commons.compress.archivers.ArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; @@ -10,16 +10,19 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.Test; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * @author lulf @@ -37,6 +40,7 @@ public class CompressedApplicationInputStreamTest { File app = new File("src/test/resources/deploy/validapp"); writeFileToTar(taos, new File(app, "services.xml")); writeFileToTar(taos, new File(app, "hosts.xml")); + writeFileToTar(taos, new File(app, "deployment.xml")); taos.close(); return outFile; } @@ -55,14 +59,8 @@ public class CompressedApplicationInputStreamTest { void assertTestApp(File outApp) { String [] files = outApp.list(); - assertThat(files.length, is(2)); - if ("hosts.xml".equals(files[0])) { - assertThat(files[1], is("services.xml")); - } else if ("hosts.xml".equals(files[1])) { - assertThat(files[0], is("services.xml")); - } else { - fail("Both services.xml and hosts.xml should be contained in the unpacked application"); - } + assertThat(files.length, is(3)); + assertThat(Arrays.asList(files), containsInAnyOrder(ImmutableList.of(is("hosts.xml"), is("services.xml"), is("deployment.xml")))); } @Test @@ -88,6 +86,10 @@ public class CompressedApplicationInputStreamTest { archiveOutputStream.putArchiveEntry(archiveOutputStream.createArchiveEntry(file, "application/" + file.getName())); ByteStreams.copy(new FileInputStream(file), archiveOutputStream); archiveOutputStream.closeArchiveEntry(); + file = new File(app, "deployment.xml"); + archiveOutputStream.putArchiveEntry(archiveOutputStream.createArchiveEntry(file, "application/" + file.getName())); + ByteStreams.copy(new FileInputStream(file), archiveOutputStream); + archiveOutputStream.closeArchiveEntry(); archiveOutputStream.close(); @@ -134,9 +136,10 @@ public class CompressedApplicationInputStreamTest { new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(gzFile)))); File outApp = unpacked.decompress(); List<File> files = Arrays.asList(outApp.listFiles()); - assertThat(files.size(), is(4)); + assertThat(files.size(), is(5)); assertTrue(files.contains(new File(outApp, "services.xml"))); assertTrue(files.contains(new File(outApp, "hosts.xml"))); + assertTrue(files.contains(new File(outApp, "deployment.xml"))); assertTrue(files.contains(new File(outApp, "searchdefinitions"))); assertTrue(files.contains(new File(outApp, "external"))); File sd = files.get(files.indexOf(new File(outApp, "searchdefinitions"))); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index 892c821950e..5552758a0a6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -292,7 +292,7 @@ public class ApplicationHandlerTest { Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) .build(); - Tenants tenants = new Tenants(componentRegistry, Metrics.createTestMetrics()); // Creates the application path element in zk + Tenants tenants = new Tenants(componentRegistry); // Creates the application path element in zk tenants.addTenant(tenantName); Tenant tenant = tenants.getTenant(tenantName); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 6b25772f85d..9d04b7e982d 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -19,10 +19,8 @@ import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.logging.AccessLog; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.path.Path; import com.yahoo.slime.JsonFormat; import com.yahoo.vespa.config.server.ApplicationRepository; -import com.yahoo.vespa.config.server.PathProvider; import com.yahoo.vespa.config.server.SuperModelGenerationCounter; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; @@ -45,6 +43,7 @@ import com.yahoo.vespa.config.server.session.SessionContext; import com.yahoo.vespa.config.server.session.SessionFactory; import com.yahoo.vespa.config.server.session.SessionTest; import com.yahoo.vespa.config.server.session.SessionZooKeeperClient; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; @@ -83,7 +82,6 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { private Curator curator; private RemoteSessionRepo remoteSessionRepo; private LocalSessionRepo localRepo; - private PathProvider pathProvider; private TenantApplications applicationRepo; private MockProvisioner hostProvisioner; @@ -95,7 +93,6 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { configCurator = ConfigCurator.create(curator); localRepo = new LocalSessionRepo(Clock.systemUTC()); pathPrefix = "/application/v2/tenant/" + tenant + "/session/"; - pathProvider = new PathProvider(Path.createRoot()); hostProvisioner = new MockProvisioner(); } @@ -213,7 +210,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient, Clock clock) throws IOException { zkClient.writeStatus(status); - ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, pathProvider.getSessionDirs().append(String.valueOf(sessionId))); + ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, Tenants.getSessionsPath(tenant).append(String.valueOf(sessionId))); VespaModelFactory modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); zkC.write(Collections.singletonMap(modelFactory.getVersion(), new MockFileRegistry())); zkC.write(AllocatedHosts.withHosts(Collections.emptySet())); @@ -318,7 +315,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { } ActivateRequest invoke(boolean createLocalSession) throws Exception { - SessionZooKeeperClient zkClient = new MockSessionZKClient(curator, pathProvider.getSessionDirs().append(String.valueOf(sessionId)), + SessionZooKeeperClient zkClient = new MockSessionZKClient(curator, tenant, sessionId, Optional.of(AllocatedHosts.withHosts(Collections.singleton(new HostSpec("bar", Collections.emptyList()))))); session = createRemoteSession(sessionId, initialStatus, zkClient, clock); if (createLocalSession) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java index 7fe7b350734..310342e81f1 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java @@ -3,11 +3,11 @@ package com.yahoo.vespa.config.server.http.v2; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.application.provider.FilesApplicationPackage; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.logging.AccessLog; -import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.application.TenantApplications; @@ -15,24 +15,33 @@ import com.yahoo.vespa.config.server.http.CompressedApplicationInputStreamTest; import com.yahoo.vespa.config.server.http.HandlerTest; import com.yahoo.vespa.config.server.http.HttpErrorResponse; import com.yahoo.vespa.config.server.http.SessionHandlerTest; -import com.yahoo.vespa.config.server.session.*; +import com.yahoo.vespa.config.server.session.LocalSessionRepo; +import com.yahoo.vespa.config.server.session.SessionFactory; import com.yahoo.vespa.config.server.tenant.Tenants; -import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; import java.time.Clock; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static com.yahoo.jdisc.Response.Status.*; +import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; +import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; +import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; +import static com.yahoo.jdisc.Response.Status.OK; +import static com.yahoo.jdisc.http.HttpRequest.Method.GET; +import static com.yahoo.jdisc.http.HttpRequest.Method.POST; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.*; - -import static com.yahoo.jdisc.http.HttpRequest.Method.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author hmusum @@ -168,7 +177,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { assertTrue(applicationPackage.exists()); final File[] files = applicationPackage.listFiles(); assertNotNull(files); - assertThat(files.length, is(2)); + assertThat(files.length, is(3)); } @Test diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java index 428cd16508f..7900a67bddd 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java @@ -20,7 +20,6 @@ import com.yahoo.slime.JsonDecoder; import com.yahoo.slime.Slime; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.config.server.ApplicationRepository; -import com.yahoo.vespa.config.server.PathProvider; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.host.HostRegistry; @@ -33,6 +32,7 @@ import com.yahoo.vespa.config.server.http.*; import com.yahoo.vespa.config.server.session.*; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; +import org.apache.commons.lang.NullArgumentException; import org.junit.Before; import org.junit.Test; @@ -149,14 +149,13 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { */ private RemoteSessionRepo fromLocalSessionRepo(LocalSessionRepo localRepo, Clock clock) { RemoteSessionRepo remoteRepo = new RemoteSessionRepo(); - PathProvider pathProvider = new PathProvider(Path.createRoot()); for (LocalSession ls : localRepo.listSessions()) { - zooKeeperClient = new MockSessionZKClient(curator, pathProvider.getSessionDirs().append(String.valueOf(ls.getSessionId()))); + zooKeeperClient = new MockSessionZKClient(curator, tenant, ls.getSessionId()); if (ls.getStatus()!=null) zooKeeperClient.writeStatus(ls.getStatus()); - RemoteSession remSess = new RemoteSession(TenantName.from("default"), ls.getSessionId(), + RemoteSession remSess = new RemoteSession(tenant, ls.getSessionId(), new TestComponentRegistry.Builder().curator(curator).build(), - zooKeeperClient, + zooKeeperClient, clock); remoteRepo.addSession(remSess); } @@ -239,8 +238,8 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_that_preparing_with_multiple_tenants_work() throws Exception { // Need different repos for 'default' tenant as opposed to the 'test' tenant LocalSessionRepo localRepoDefault = new LocalSessionRepo(Clock.systemUTC()); - final TenantName tenantName = TenantName.defaultName(); - addTenant(tenantName, localRepoDefault, new RemoteSessionRepo(), new MockSessionFactory()); + final TenantName defaultTenant = TenantName.defaultName(); + addTenant(defaultTenant, localRepoDefault, new RemoteSessionRepo(), new MockSessionFactory()); addTestTenant(); final SessionHandler handler = createHandler(builder); @@ -248,7 +247,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { // Deploy with default tenant MockSession session = new MockSession(sessionId, null); localRepoDefault.addSession(session); - pathPrefix = "/application/v2/tenant/default/session/"; + pathPrefix = "/application/v2/tenant/" + defaultTenant + "/session/"; HttpResponse response = handler.handle( SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, sessionId)); @@ -317,7 +316,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { @Test public void test_out_of_capacity_response() throws InterruptedException, IOException { - String message = "No nodes available"; + String message = "Internal error"; SessionThrowingException session = new SessionThrowingException(new OutOfCapacityException(message)); localRepo.addSession(session); HttpResponse response = createHandler() @@ -329,6 +328,19 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } @Test + public void test_that_nullpointerexception_gives_internal_server_error() throws InterruptedException, IOException { + String message = "No nodes available"; + SessionThrowingException session = new SessionThrowingException(new NullPointerException(message)); + localRepo.addSession(session); + HttpResponse response = createHandler() + .handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + assertEquals(500, response.getStatus()); + Slime data = getData(response); + assertThat(data.get().field("error-code").asString(), is(HttpErrorResponse.errorCodes.INTERNAL_SERVER_ERROR.name())); + assertThat(data.get().field("message").asString(), is(message)); + } + + @Test public void test_application_lock_failure() throws InterruptedException, IOException { String message = "Timed out after waiting PT1M to acquire lock '/provision/v1/locks/foo/bar/default'"; SessionThrowingException session = diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java index d82f62cfc1a..9dbb193ab3d 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java @@ -9,7 +9,6 @@ import java.util.concurrent.Executor; import com.yahoo.vespa.config.server.*; import com.yahoo.vespa.config.server.http.SessionResponse; -import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.config.server.tenant.Tenants; import org.junit.After; import org.junit.Before; @@ -35,7 +34,7 @@ public class TenantTest extends TestWithCurator { } protected Tenants createTenants() throws Exception { - return new Tenants(new TestComponentRegistry.Builder().curator(curator).build(), Metrics.createTestMetrics()); + return new Tenants(new TestComponentRegistry.Builder().curator(curator).build()); } protected Executor testExecutor() { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java index 2b1000c2211..16ce605d4d1 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java @@ -4,10 +4,9 @@ package com.yahoo.vespa.config.server.http.v2; import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.yahoo.config.provision.TenantName; -import com.yahoo.path.Path; -import com.yahoo.vespa.config.server.*; +import com.yahoo.vespa.config.server.GlobalComponentRegistry; +import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; -import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.session.RemoteSessionRepo; import com.yahoo.vespa.config.server.tenant.Tenant; @@ -19,7 +18,7 @@ import java.util.*; /** * Test utility for creating tenants used for testing and setup wiring of tenant stuff. * - * @author lulf + * @author Ulf Lilleengen * @since 5.1 */ public class TestTenantBuilder { @@ -33,7 +32,7 @@ public class TestTenantBuilder { public TenantBuilder createTenant(TenantName tenantName) { MemoryTenantApplications applicationRepo = new MemoryTenantApplications(); - TenantBuilder builder = TenantBuilder.create(componentRegistry, tenantName, Path.createRoot().append(tenantName.value())) + TenantBuilder builder = TenantBuilder.create(componentRegistry, tenantName) .withSessionFactory(new SessionCreateHandlerTest.MockSessionFactory()) .withLocalSessionRepo(new LocalSessionRepo(componentRegistry.getClock())) .withRemoteSessionRepo(new RemoteSessionRepo()) @@ -57,6 +56,6 @@ public class TestTenantBuilder { } } }); - return new Tenants(componentRegistry, Metrics.createTestMetrics(), tenantList); + return new Tenants(componentRegistry, tenantList); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java index 474b93f6972..df8ed405fe3 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java @@ -88,8 +88,9 @@ public class LbServicesProducerTest { private LbServicesConfig createModelAndGetLbServicesConfig(RegionName regionName) throws IOException, SAXException { final Zone zone = new Zone(Environment.prod, regionName); - Map<TenantName, Map<ApplicationId, ApplicationInfo>> testModel = createTestModel(new DeployState.Builder(). - properties(new DeployProperties.Builder().zone(zone).build())); + Map<TenantName, Map<ApplicationId, ApplicationInfo>> testModel = createTestModel(new DeployState.Builder() + .properties(new DeployProperties.Builder().zone(zone).build()) + .zone(zone)); return getLbServicesConfig(new Zone(Environment.prod, regionName), testModel); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponseTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponseTest.java index 122deadb841..0126a9e2f29 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponseTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/DelayedConfigResponseTest.java @@ -12,8 +12,6 @@ import com.yahoo.vespa.config.protocol.JRTServerConfigRequest; import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3; import com.yahoo.vespa.config.protocol.Trace; import com.yahoo.vespa.config.server.GetConfigContext; -import com.yahoo.vespa.config.server.rpc.DelayedConfigResponses; -import com.yahoo.vespa.config.server.rpc.MockRpc; import org.junit.Test; import java.util.Collections; @@ -58,7 +56,7 @@ public class DelayedConfigResponseTest { DelayedConfigResponses responses = new DelayedConfigResponses(rpc, 1, false); responses.delayResponse(createRequest("foolio", "md5", "myid", "mymd5", 3, 100000, "bar"), context); assertThat(responses.size(), is(1)); - responses.allDelayedResponses().get(0).cancel(); + responses.allDelayedResponses().get(0).cancelAndRemove(); assertThat(responses.size(), is(0)); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpc.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpc.java index a08514e8afb..4c2a4b56751 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpc.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpc.java @@ -2,11 +2,13 @@ package com.yahoo.vespa.config.server.rpc; import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.vespa.config.protocol.ConfigResponse; import com.yahoo.vespa.config.protocol.JRTServerConfigRequest; import com.yahoo.vespa.config.server.GetConfigContext; +import com.yahoo.vespa.config.server.filedistribution.FileServer; import com.yahoo.vespa.config.server.host.ConfigRequestHostLivenessTracker; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.monitoring.Metrics; @@ -37,7 +39,7 @@ public class MockRpc extends RpcServer { public MockRpc(int port, boolean createDefaultTenant, boolean pretendToHaveLoadedAnyApplication) { super(createConfig(port), null, Metrics.createTestMetrics(), - new HostRegistries(), new ConfigRequestHostLivenessTracker()); + new HostRegistries(), new ConfigRequestHostLivenessTracker(), new FileServer(FileDistribution.getDefaultFileDBPath())); if (createDefaultTenant) { onTenantCreate(TenantName.from("default"), new MockTenantProvider(pretendToHaveLoadedAnyApplication)); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/TestWithRpc.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/TestWithRpc.java index fa6adb64a8a..12dc584f055 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/TestWithRpc.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/TestWithRpc.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.config.server.rpc; import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.provision.HostLivenessTracker; import com.yahoo.config.provision.TenantName; import com.yahoo.jrt.Request; @@ -12,6 +13,7 @@ import com.yahoo.net.HostName; import com.yahoo.test.ManualClock; import com.yahoo.vespa.config.GenerationCounter; import com.yahoo.vespa.config.server.*; +import com.yahoo.vespa.config.server.filedistribution.FileServer; import com.yahoo.vespa.config.server.host.ConfigRequestHostLivenessTracker; import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.monitoring.Metrics; @@ -88,7 +90,7 @@ public class TestWithRpc { emptyNodeFlavors(), generationCounter)), Metrics.createTestMetrics(), new HostRegistries(), - hostLivenessTracker); + hostLivenessTracker, new FileServer(FileDistribution.getDefaultFileDBPath())); rpcServer.onTenantCreate(TenantName.from("default"), tenantProvider); t = new Thread(rpcServer); t.start(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java index 5753b2959f7..3d34d08edeb 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java @@ -2,10 +2,11 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.config.model.application.provider.FilesApplicationPackage; -import com.yahoo.path.Path; import com.yahoo.test.ManualClock; -import com.yahoo.vespa.config.server.*; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.config.server.GlobalComponentRegistry; +import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.TestWithCurator; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.io.IOUtils; @@ -20,13 +21,12 @@ import java.io.File; import java.time.Duration; import java.time.Instant; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; /** - * @author lulf + * @author Ulf Lilleengen * @since 5.1 */ public class LocalSessionRepoTest extends TestWithCurator { @@ -51,10 +51,7 @@ public class LocalSessionRepoTest extends TestWithCurator { } clock = new ManualClock(Instant.ofEpochSecond(1)); LocalSessionLoader loader = new SessionFactoryImpl(globalComponentRegistry, - new SessionCounter(globalComponentRegistry.getCurator(), - Path.fromString("counter"), - Path.fromString("sessions")), - Path.createRoot(), + new SessionCounter(globalComponentRegistry.getCurator(), tenantName), new MemoryTenantApplications(), tenantFileSystemDirs, new HostRegistry<>(), tenantName); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java index c6099e724bc..b98fa49ac26 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java @@ -13,6 +13,7 @@ import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger; import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.deploy.ZooKeeperClient; import com.yahoo.vespa.config.server.host.HostRegistry; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; @@ -108,13 +109,15 @@ public class LocalSessionTest { @Test public void require_that_session_can_be_deleted() throws Exception { - LocalSession session = createSession(TenantName.defaultName(), 3); - assertTrue(configCurator.exists("/3")); + TenantName tenantName = TenantName.defaultName(); + LocalSession session = createSession(tenantName, 3); + String sessionNode = Tenants.getSessionsPath(tenantName).append(String.valueOf(3)).getAbsolute(); + assertTrue(configCurator.exists(sessionNode)); assertTrue(new File(tenantFileSystemDirs.sessionsPath(), "3").exists()); long gen = superModelGenerationCounter.get(); session.delete(); assertThat(superModelGenerationCounter.get(), is(gen + 1)); - assertFalse(configCurator.exists("/3")); + assertFalse(configCurator.exists(sessionNode)); assertFalse(new File(tenantFileSystemDirs.sessionsPath(), "3").exists()); } @@ -155,10 +158,9 @@ public class LocalSessionTest { } private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer, Optional<AllocatedHosts> allocatedHosts) throws Exception { - Path sessionPath = Path.fromString("/" + sessionId); - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, sessionPath, allocatedHosts); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenant, sessionId, allocatedHosts); zkc.createWriteStatusTransaction(Session.Status.NEW).commit(); - ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, sessionPath); + ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, Tenants.getSessionsPath(tenant).append(String.valueOf(sessionId))); if (allocatedHosts.isPresent()) { zkClient.write(allocatedHosts.get()); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java index 62b0ecbada2..a4331216334 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java @@ -4,8 +4,10 @@ package com.yahoo.vespa.config.server.session; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.config.provision.TenantName; import com.yahoo.transaction.Transaction; import com.yahoo.path.Path; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; @@ -22,17 +24,17 @@ public class MockSessionZKClient extends SessionZooKeeperClient { private Optional<AllocatedHosts> info = Optional.empty(); private Session.Status sessionStatus; - public MockSessionZKClient(Curator curator, Path sessionPath) { - this(curator, sessionPath, (ApplicationPackage)null); + public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId) { + this(curator, tenantName, sessionId, (ApplicationPackage)null); } - public MockSessionZKClient(Curator curator, Path sessionPath, Optional<AllocatedHosts> allocatedHosts) { - this(curator, sessionPath); + public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, Optional<AllocatedHosts> allocatedHosts) { + this(curator, tenantName, sessionId); this.info = allocatedHosts; } - public MockSessionZKClient(Curator curator, Path sessionPath, ApplicationPackage application) { - super(curator, sessionPath); + public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, ApplicationPackage application) { + super(curator, Tenants.getSessionsPath(tenantName).append(String.valueOf(sessionId))); this.app = application; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java index 462062ce8a8..878339bd703 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java @@ -2,18 +2,22 @@ package com.yahoo.vespa.config.server.session; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; import com.yahoo.text.Utf8; import com.yahoo.transaction.Transaction; -import com.yahoo.vespa.config.server.*; +import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.TestWithCurator; import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantBuilder; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; import org.junit.Before; import org.junit.Test; @@ -27,33 +31,35 @@ import java.util.concurrent.TimeUnit; import java.util.function.LongPredicate; /** - * @author lulf + * @author Ulf Lilleengen * @since 5.1 */ public class RemoteSessionRepoTest extends TestWithCurator { + private static final TenantName tenantName = TenantName.defaultName(); + private RemoteSessionRepo remoteSessionRepo; @Before public void setupFacade() throws Exception { - createSession(2l, false); - createSession(3l, false); - curator.create(Path.fromString("/applications")); - curator.create(Path.fromString("/sessions")); - Tenant tenant = TenantBuilder.create(new TestComponentRegistry.Builder().curator(curator).build(), - TenantName.defaultName(), - Path.createRoot()).build(); + Tenant tenant = TenantBuilder.create(new TestComponentRegistry.Builder() + .curator(curator) + .build(), + tenantName) + .build(); this.remoteSessionRepo = tenant.getRemoteSessionRepo(); + curator.create(Tenants.getTenantPath(tenantName).append("/applications")); + curator.create(Tenants.getSessionsPath(tenantName)); + createSession(1l, false); + createSession(2l, false); } private void createSession(long sessionId, boolean wait) { - createSession("", sessionId, wait); + createSession(sessionId, wait, tenantName); } - - private void createSession(String root, long sessionId, boolean wait) { - Path sessionsPath = Path.fromString(root).append("sessions"); - curator.create(sessionsPath); + private void createSession(long sessionId, boolean wait, TenantName tenantName) { + Path sessionsPath = Tenants.getSessionsPath(tenantName); SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, sessionsPath.append(String.valueOf(sessionId))); zkc.createNewSession(System.currentTimeMillis(), TimeUnit.MILLISECONDS); if (wait) { @@ -64,27 +70,28 @@ public class RemoteSessionRepoTest extends TestWithCurator { @Test public void testInitialize() { + assertSessionExists(1l); assertSessionExists(2l); - assertSessionExists(3l); } @Test public void testCreateSession() throws Exception { - createSession(0l, true); - assertSessionExists(0l); + createSession(3l, true); + assertSessionExists(3l); } @Test public void testSessionStateChange() throws Exception { - Path session = Path.fromString("/sessions/0"); - createSession(0l, true); - assertSessionStatus(0l, Session.Status.NEW); - assertStatusChange(0l, Session.Status.PREPARE); - assertStatusChange(0l, Session.Status.ACTIVATE); + long sessionId = 3L; + createSession(sessionId, true); + assertSessionStatus(sessionId, Session.Status.NEW); + assertStatusChange(sessionId, Session.Status.PREPARE); + assertStatusChange(sessionId, Session.Status.ACTIVATE); + Path session = Tenants.getSessionsPath(tenantName).append("" + sessionId); curator.delete(session); - assertSessionRemoved(0l); - assertNull(remoteSessionRepo.getSession(0l)); + assertSessionRemoved(sessionId); + assertNull(remoteSessionRepo.getSession(sessionId)); } // If reading a session throws an exception it should be handled and not prevent other applications @@ -93,25 +100,25 @@ public class RemoteSessionRepoTest extends TestWithCurator { // throw an exception). @Test public void testBadApplicationRepoOnActivate() throws Exception { + long sessionId = 3L; TenantApplications applicationRepo = new FailingTenantApplications(); - curator.framework().create().forPath("/mytenant"); - Tenant tenant = TenantBuilder.create(new TestComponentRegistry.Builder().curator(curator).build(), - TenantName.from("mytenant"), - Path.fromString("mytenant")) + TenantName mytenant = TenantName.from("mytenant"); + Tenant tenant = TenantBuilder.create(new TestComponentRegistry.Builder().curator(curator).build(), mytenant) .withApplicationRepo(applicationRepo) .build(); + curator.create(Tenants.getSessionsPath(mytenant)); remoteSessionRepo = tenant.getRemoteSessionRepo(); assertThat(remoteSessionRepo.listSessions().size(), is(0)); - createSession("/mytenant", 2l, true); + createSession(sessionId, true, mytenant); assertThat(remoteSessionRepo.listSessions().size(), is(1)); } private void assertStatusChange(long sessionId, Session.Status status) throws Exception { - Path statePath = Path.fromString("/sessions/" + sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH); + Path statePath = Tenants.getSessionsPath(tenantName).append("" + sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH); curator.create(statePath); curatorFramework.setData().forPath(statePath.getAbsolute(), Utf8.toBytes(status.toString())); System.out.println("Setting status " + status + " for " + sessionId); - assertSessionStatus(0l, status); + assertSessionStatus(sessionId, status); } private void assertSessionRemoved(long sessionId) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java index 44f304847ba..9598a9262f0 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java @@ -11,10 +11,8 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.Version; -import com.yahoo.path.Path; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; -import com.yahoo.vespa.config.server.PathProvider; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.PermanentApplicationPackage; import com.yahoo.vespa.curator.mock.MockCurator; @@ -49,13 +47,13 @@ import static org.junit.Assert.assertTrue; */ public class RemoteSessionTest { + private static final TenantName tenantName = TenantName.from("default"); + private Curator curator; - private PathProvider pathProvider; @Before public void setupTest() throws Exception { curator = new MockCurator(); - pathProvider = new PathProvider(Path.createRoot()); } @Test @@ -180,7 +178,7 @@ public class RemoteSessionTest { okFactory.vespaVersion = Version.fromIntValues(2, 0, 0); okFactory.throwOnLoad = false; - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(3), application); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, 3, application); RemoteSession session = createSession(3, zkc, Arrays.asList(okFactory, failingFactory), failingFactory.clock()); session.loadPrepared(); @@ -189,7 +187,7 @@ public class RemoteSessionTest { @Test public void require_that_session_status_is_updated() throws IOException, SAXException { - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(3)); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, 3); RemoteSession session = createSession(3, zkc, Clock.systemUTC()); assertThat(session.getStatus(), is(Session.Status.NEW)); zkc.writeStatus(Session.Status.PREPARE); @@ -203,7 +201,7 @@ public class RemoteSessionTest { MockModelFactory mockModelFactory = new MockModelFactory(); try { int sessionId = 3; - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(sessionId)); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, sessionId); createSession(sessionId, zkc, Collections.singletonList(mockModelFactory), permanentApp, mockModelFactory.clock()).ensureApplicationLoaded(); } catch (Exception e) { e.printStackTrace(); @@ -220,7 +218,7 @@ public class RemoteSessionTest { return createSession(sessionId, zkc, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())), clock); } private RemoteSession createSession(long sessionId, List<ModelFactory> modelFactories, Clock clock) { - SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(sessionId)); + SessionZooKeeperClient zkc = new MockSessionZKClient(curator, tenantName, sessionId); return createSession(sessionId, zkc, modelFactories, clock); } @@ -238,7 +236,8 @@ public class RemoteSessionTest { if (permanentApplicationPackage.isPresent()) registryBuilder.permanentApplicationPackage(permanentApplicationPackage.get()); - return new RemoteSession(TenantName.from("default"), sessionId, + + return new RemoteSession(tenantName, sessionId, registryBuilder.build(), zkc, clock); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java index 5dc529e3381..2069ae48d76 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java @@ -126,7 +126,6 @@ public class SessionPreparerTest extends TestWithCurator { new PrepareParams.Builder().dryRun(true).timeoutBudget(TimeoutBudgetTest.day()).build(), Optional.empty(), tenantPath, Instant.now()); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().sendDeployedFilesCalled, is(0)); - assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().limitSendingOfDeployedFilesToCalled, is(0)); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().reloadDeployFileDistributorCalled, is(0)); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java index 1bb25bc37db..01cb90721f3 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java @@ -1,8 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.session; -import com.yahoo.path.Path; -import com.yahoo.vespa.config.server.PathProvider; import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Test; @@ -34,10 +32,10 @@ public class SessionRepoTest { } private class TestSession extends Session { - public TestSession(long sessionId) { - super(TenantName.from("default"), + TestSession(long sessionId) { + super(TenantName.defaultName(), sessionId, - new MockSessionZKClient(new MockCurator(), new PathProvider(Path.createRoot()).getSessionDir(sessionId))); + new MockSessionZKClient(new MockCurator(), TenantName.defaultName(), sessionId)); } } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java index 91cf6e79165..dc6268c5a25 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java @@ -11,7 +11,6 @@ import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.Version; import com.yahoo.io.IOUtils; -import com.yahoo.path.Path; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.GetConfigRequest; @@ -20,7 +19,6 @@ import com.yahoo.vespa.config.protocol.DefContent; import com.yahoo.vespa.config.protocol.VespaVersion; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.host.HostRegistries; -import com.yahoo.vespa.config.server.PathProvider; import com.yahoo.vespa.config.server.ReloadListener; import com.yahoo.vespa.config.server.ServerCache; import com.yahoo.vespa.config.server.TestComponentRegistry; @@ -88,7 +86,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { private void feedApp(File appDir, long sessionId, ApplicationId appId) throws IOException { SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, - new PathProvider(Path.createRoot()).getSessionDir(sessionId), + Tenants.getSessionsPath(tenant).append(String.valueOf(sessionId)), new TestConfigDefinitionRepo(), "", Optional.empty()); zkc.writeApplicationId(appId); @@ -104,7 +102,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { private ApplicationSet reloadConfig(long id, String application, Clock clock) { SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, - new PathProvider(Path.createRoot()).getSessionDir(id), + Tenants.getSessionsPath(tenant).append(String.valueOf(id)), new TestConfigDefinitionRepo(), "", Optional.empty()); zkc.writeApplicationId(new ApplicationId.Builder().tenant(tenant).applicationName(application).build()); @@ -187,7 +185,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { public void testResolveForAppId() { long id = 1l; SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, - new PathProvider(Path.createRoot()).getSessionDir(id), + Tenants.getSessionsPath(tenant).append(String.valueOf(id)), new TestConfigDefinitionRepo(), "", Optional.empty()); ApplicationId appId = new ApplicationId.Builder() @@ -231,7 +229,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { private void feedAndReloadApp(File appDir, long sessionId, ApplicationId appId) throws IOException { feedApp(appDir, sessionId, appId); - SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, new PathProvider(Path.createRoot()).getSessionDir(sessionId)); + SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, Tenants.getSessionsPath(tenant).append(String.valueOf(sessionId))); zkc.writeApplicationId(appId); RemoteSession session = new RemoteSession(tenant, sessionId, componentRegistry, zkc, Clock.systemUTC()); server.reloadConfig(session.ensureApplicationLoaded()); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java index d1724986d5e..e650997b7e0 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java @@ -11,7 +11,6 @@ import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.TestWithCurator; import com.yahoo.vespa.config.server.application.Application; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; -import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.model.VespaModel; import org.junit.After; import org.junit.Before; @@ -20,6 +19,7 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -46,7 +46,7 @@ public class TenantsTestCase extends TestWithCurator { listener = (TenantRequestHandlerTest.MockReloadListener)globalComponentRegistry.getReloadListener(); tenantListener = (MockTenantListener)globalComponentRegistry.getTenantListener(); tenantListener.tenantsLoaded = false; - tenants = new Tenants(globalComponentRegistry, Metrics.createTestMetrics()); + tenants = new Tenants(globalComponentRegistry); assertTrue(tenantListener.tenantsLoaded); tenants.addTenant(tenant1); tenants.addTenant(tenant2); @@ -105,19 +105,28 @@ public class TenantsTestCase extends TestWithCurator { } @Test - public void testRemove() throws Exception { + public void testDelete() throws Exception { assertNotNull(globalComponentRegistry.getCurator().framework().checkExists().forPath(tenants.tenantZkPath(tenant1))); tenants.deleteTenant(tenant1); assertFalse(tenants.getAllTenantNames().contains(tenant1)); } + + @Test(expected = IllegalArgumentException.class) + public void testDeleteOfDefaultTenant() { + try { + assertNotNull(globalComponentRegistry.getCurator().framework().checkExists().forPath(tenants.tenantZkPath(TenantName.defaultName()))); + } catch (Exception e) { + fail("default tenant does not exist"); + } + tenants.deleteTenant(TenantName.defaultName()); + } @Test public void testTenantsChanged() throws Exception { tenants.close(); // close the Tenants instance created in setupSession, we do not want to use one with a PatchChildrenCache listener - tenants = new Tenants(globalComponentRegistry, Metrics.createTestMetrics(), new ArrayList<>()); + tenants = new Tenants(globalComponentRegistry, new ArrayList<>()); TenantName defaultTenant = TenantName.defaultName(); tenants.addTenant(tenant2); - tenants.addTenant(defaultTenant); tenants.createTenants(); Set<TenantName> allTenants = tenants.getAllTenantNames(); assertTrue(allTenants.contains(tenant2)); @@ -125,12 +134,10 @@ public class TenantsTestCase extends TestWithCurator { assertTrue(allTenants.contains(defaultTenant)); tenants.deleteTenant(tenant1); tenants.deleteTenant(tenant2); - tenants.deleteTenant(defaultTenant); tenants.createTenants(); allTenants = tenants.getAllTenantNames(); assertFalse(allTenants.contains(tenant1)); assertFalse(allTenants.contains(tenant2)); - assertFalse(allTenants.contains(defaultTenant)); TenantName foo = TenantName.from("foo"); TenantName bar = TenantName.from("bar"); tenants.addTenant(tenant2); @@ -145,25 +152,24 @@ public class TenantsTestCase extends TestWithCurator { @Test public void testTenantWatching() throws Exception { - TestComponentRegistry reg = new TestComponentRegistry.Builder().curator(curator).build(); - Tenants t = new Tenants(reg, Metrics.createTestMetrics()); + TenantName newTenant = TenantName.from("newTenant"); + List<TenantName> expectedTenants = Arrays.asList(TenantName.defaultName(), newTenant); try { - assertTrue(t.getAllTenantNames().contains(TenantName.defaultName())); - reg.getCurator().framework().create().forPath(tenants.tenantZkPath(TenantName.from("newTenant"))); + tenants.addTenant(newTenant); // Poll for the watcher to pick up the tenant from zk, and add it int tries=0; while(true) { - if (tries > 500) fail("Didn't react on watch"); - if (t.getAllTenantNames().contains(TenantName.from("newTenant"))) { - return; + if (tries > 5000) fail("Didn't react on watch"); + if (tenants.getAllTenantNames().containsAll(expectedTenants)) { + break; } tries++; - Thread.sleep(100); + Thread.sleep(10); } } finally { - t.close(); + assertTrue(tenants.getAllTenantNames().containsAll(expectedTenants)); + tenants.close(); } } - } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestWithTenant.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestWithTenant.java index c11480f5335..4573f785842 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestWithTenant.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TestWithTenant.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.config.server.tenant; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.TestWithCurator; -import com.yahoo.vespa.config.server.monitoring.Metrics; import org.junit.Before; /** @@ -19,8 +18,7 @@ public class TestWithTenant extends TestWithCurator { @Before public void setupTenant() throws Exception { - final Metrics metrics = Metrics.createTestMetrics(); - tenants = new Tenants(new TestComponentRegistry.Builder().curator(curator).metrics(metrics).build(), metrics); + tenants = new Tenants(new TestComponentRegistry.Builder().curator(curator).build()); tenant = tenants.defaultTenant(); } diff --git a/configserver/src/test/resources/deploy/advancedapp/deployment.xml b/configserver/src/test/resources/deploy/advancedapp/deployment.xml new file mode 100644 index 00000000000..fa1d1388e67 --- /dev/null +++ b/configserver/src/test/resources/deploy/advancedapp/deployment.xml @@ -0,0 +1 @@ +<deployment version='1.0'/>
\ No newline at end of file diff --git a/configserver/src/test/resources/deploy/app/deployment.xml b/configserver/src/test/resources/deploy/app/deployment.xml new file mode 100644 index 00000000000..fa1d1388e67 --- /dev/null +++ b/configserver/src/test/resources/deploy/app/deployment.xml @@ -0,0 +1 @@ +<deployment version='1.0'/>
\ No newline at end of file diff --git a/configserver/src/test/resources/deploy/validapp/deployment.xml b/configserver/src/test/resources/deploy/validapp/deployment.xml new file mode 100644 index 00000000000..fa1d1388e67 --- /dev/null +++ b/configserver/src/test/resources/deploy/validapp/deployment.xml @@ -0,0 +1 @@ +<deployment version='1.0'/>
\ No newline at end of file |