diff options
681 files changed, 13546 insertions, 6980 deletions
diff --git a/application/pom.xml b/application/pom.xml index f9f3e8394af..20b754fc236 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -134,7 +134,12 @@ <artifactId>httpclient</artifactId> <scope>test</scope> </dependency> - + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>testutil</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> <!-- These dependencies are necessary in test classpath when using jdisc_http_filters --> <dependency> diff --git a/application/src/test/java/com/yahoo/application/container/ContainerModelEvaluationTest.java b/application/src/test/java/com/yahoo/application/container/ContainerModelEvaluationTest.java index 66a7ae579fa..6a2b7945d73 100644 --- a/application/src/test/java/com/yahoo/application/container/ContainerModelEvaluationTest.java +++ b/application/src/test/java/com/yahoo/application/container/ContainerModelEvaluationTest.java @@ -7,6 +7,7 @@ import com.yahoo.application.container.handler.Request; import com.yahoo.application.container.handler.Response; import com.yahoo.tensor.Tensor; import com.yahoo.tensor.TensorType; +import com.yahoo.test.json.JsonTestHelper; import org.junit.Ignore; import org.junit.Test; @@ -64,7 +65,7 @@ public class ContainerModelEvaluationTest { private void assertResponse(String url, String expectedResponse, JDisc jdisc) { try { Response response = jdisc.handleRequest(new Request(url)); - assertEquals(expectedResponse, response.getBodyAsString()); + JsonTestHelper.assertJsonEquals(expectedResponse, response.getBodyAsString()); assertEquals(200, response.getStatus()); } catch (CharacterCodingException e) { diff --git a/build_settings.cmake b/build_settings.cmake index 7d8ba11f8a1..b1b0b065771 100644 --- a/build_settings.cmake +++ b/build_settings.cmake @@ -21,20 +21,45 @@ set(C_WARN_OPTS "-Winline -Wuninitialized -Werror -Wall -W -Wchar-subscripts -Wc # Warnings that are specific to C++ compilation # Note: this is not a union of C_WARN_OPTS, since CMAKE_CXX_FLAGS already includes CMAKE_C_FLAGS, which in turn includes C_WARN_OPTS transitively -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") set(CXX_SPECIFIC_WARN_OPTS "-Wnon-virtual-dtor -Wformat-security -Wno-overloaded-virtual") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-delete-null-pointer-checks") + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + set(VESPA_ATOMIC_LIB "") + set(VESPA_GCC_LIB "") + set(VESPA_STDCXX_FS_LIB "") + else() + set(VESPA_ATOMIC_LIB "atomic") + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0) + set(VESPA_GCC_LIB "gcc") + set(VESPA_STDCXX_FS_LIB "stdc++fs") + else() + set(VESPA_GCC_LIB "") + set(VESPA_STDCXX_FS_LIB "") + endif() + endif() else() set(CXX_SPECIFIC_WARN_OPTS "-Wsuggest-override -Wnon-virtual-dtor -Wformat-security") + set(VESPA_ATOMIC_LIB "atomic") + set(VESPA_GCC_LIB "gcc") + set(VESPA_STDCXX_FS_LIB "stdc++fs") endif() # C and C++ compiler flags set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 -fno-omit-frame-pointer ${C_WARN_OPTS} -fPIC ${VESPA_CXX_ABI_FLAGS} -DBOOST_DISABLE_ASSERTS ${VESPA_CPU_ARCH_FLAGS} -mtune=intel ${EXTRA_C_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${CXX_SPECIFIC_WARN_OPTS} -std=c++1z -fvisibility-inlines-hidden -fdiagnostics-color=auto ${EXTRA_CXX_FLAGS}") +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${CXX_SPECIFIC_WARN_OPTS} -std=c++1z -fdiagnostics-color=auto ${EXTRA_CXX_FLAGS}") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${CXX_SPECIFIC_WARN_OPTS} -std=c++1z -fvisibility-inlines-hidden -fdiagnostics-color=auto ${EXTRA_CXX_FLAGS}") +endif() # Linker flags if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -latomic -ldl") + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -ldl") + else() + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -latomic -ldl") + endif() else() set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--build-id -latomic -ldl -Wl,-E") endif() diff --git a/bundle-plugin-test/src/test/java/com/yahoo/BundleIT.java b/bundle-plugin-test/src/test/java/com/yahoo/BundleIT.java index 5b4fdda06f7..932b78a5c8d 100644 --- a/bundle-plugin-test/src/test/java/com/yahoo/BundleIT.java +++ b/bundle-plugin-test/src/test/java/com/yahoo/BundleIT.java @@ -18,6 +18,7 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.ZipEntry; +import static com.yahoo.osgi.maven.ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; @@ -143,9 +144,9 @@ public class BundleIT { @SuppressWarnings("unchecked") @Test public void bundle_class_path_mappings_are_generated() throws URISyntaxException, IOException { - URL mappingsUrl = getClass().getResource("/" + com.yahoo.osgi.maven.ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME); + URL mappingsUrl = getClass().getResource("/" + CLASSPATH_MAPPINGS_FILENAME); assertNotNull( - "Could not find " + ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME + " in the test output directory", + "Could not find " + CLASSPATH_MAPPINGS_FILENAME + " in the test output directory", mappingsUrl); ProjectBundleClassPaths bundleClassPaths = ProjectBundleClassPaths.load(Paths.get(mappingsUrl.toURI())); diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java index 798fea2644e..0626c786822 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java @@ -11,10 +11,14 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.jar.Manifest; import java.util.stream.Collectors; /** + * "Public package" in this context means a package that is either exported or declared as "Global-Package" + * in a bundle's manifest. + * * @author Tony Vaagenes * @author ollivir */ @@ -23,10 +27,18 @@ public class AnalyzeBundle { public final List<Export> exports; public final List<String> globals; - public PublicPackages(List<Export> exports, List<String> globals) { + PublicPackages(List<Export> exports, List<String> globals) { this.exports = exports; this.globals = globals; } + + public Set<String> exportedPackageNames() { + return exports.stream() + .map(Export::getPackageNames) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + } public static PublicPackages publicPackagesAggregated(Collection<File> jarFiles) { diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java index 13bbc63192c..9f5fd2236e7 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.plugin.classanalysis; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; import com.yahoo.container.plugin.util.Maps; @@ -10,6 +11,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * @author Tony Vaagenes @@ -19,7 +21,8 @@ public class PackageTally { private final Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap; private final Set<String> referencedPackagesUnfiltered; - public PackageTally(Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap, Set<String> referencedPackagesUnfiltered) { + @VisibleForTesting + PackageTally(Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap, Set<String> referencedPackagesUnfiltered) { this.definedPackagesMap = definedPackagesMap; this.referencedPackagesUnfiltered = referencedPackagesUnfiltered; } @@ -41,6 +44,19 @@ public class PackageTally { } /** + * Returns the set of packages that is referenced from this tally, but not included in the given set of available packages. + * + * @param definedAndExportedPackages Set of available packages (usually all packages defined in the generated bundle's project + all exported packages of dependencies) + * @return The set of missing packages, that may cause problem when the bundle is deployed in an OSGi container runtime. + */ + public Set<String> referencedPackagesMissingFrom(Set<String> definedAndExportedPackages) { + return Sets.difference(referencedPackages(), definedAndExportedPackages).stream() + .filter(pkg -> !pkg.startsWith("java.")) + .filter(pkg -> !pkg.equals(com.yahoo.api.annotations.PublicApi.class.getPackageName())) + .collect(Collectors.toSet()); + } + + /** * Represents the classes for two package tallies that are deployed as a single unit. * <p> * ExportPackageAnnotations from this has precedence over the other. diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java index fff88d413d0..a0d0e143724 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java @@ -12,8 +12,8 @@ import java.util.List; * @author Tony Vaagenes * @author ollivir */ -public class Artifacts { - public static class ArtifactSet { +class Artifacts { + static class ArtifactSet { private final List<Artifact> jarArtifactsToInclude; private final List<Artifact> jarArtifactsProvided; private final List<Artifact> nonJarArtifacts; @@ -24,20 +24,20 @@ public class Artifacts { this.nonJarArtifacts = nonJarArtifacts; } - public List<Artifact> getJarArtifactsToInclude() { + List<Artifact> getJarArtifactsToInclude() { return jarArtifactsToInclude; } - public List<Artifact> getJarArtifactsProvided() { + List<Artifact> getJarArtifactsProvided() { return jarArtifactsProvided; } - public List<Artifact> getNonJarArtifacts() { + List<Artifact> getNonJarArtifacts() { return nonJarArtifacts; } } - public static ArtifactSet getArtifacts(MavenProject project) { + static ArtifactSet getArtifacts(MavenProject project) { List<Artifact> jarArtifactsToInclude = new ArrayList<>(); List<Artifact> jarArtifactsProvided = new ArrayList<>(); @@ -63,7 +63,7 @@ public class Artifacts { return new ArtifactSet(jarArtifactsToInclude, jarArtifactsProvided, nonJarArtifactsToInclude); } - public static Collection<Artifact> getArtifactsToInclude(MavenProject project) { + static Collection<Artifact> getArtifactsToInclude(MavenProject project) { return getArtifacts(project).getJarArtifactsToInclude(); } } diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java index 9c9957ae718..04f2428d284 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java @@ -10,14 +10,12 @@ import com.yahoo.container.plugin.classanalysis.PackageTally; import com.yahoo.container.plugin.osgi.ExportPackageParser; import com.yahoo.container.plugin.osgi.ExportPackages; import com.yahoo.container.plugin.osgi.ExportPackages.Export; -import com.yahoo.container.plugin.osgi.ImportPackages; import com.yahoo.container.plugin.osgi.ImportPackages.Import; import com.yahoo.container.plugin.util.Strings; import org.apache.commons.lang3.tuple.Pair; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; @@ -41,6 +39,9 @@ import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.yahoo.container.plugin.bundle.AnalyzeBundle.publicPackagesAggregated; +import static com.yahoo.container.plugin.osgi.ExportPackages.exportsByPackageName; +import static com.yahoo.container.plugin.osgi.ImportPackages.calculateImports; import static com.yahoo.container.plugin.util.Files.allDescendantFiles; import static com.yahoo.container.plugin.util.IO.withFileOutputStream; import static com.yahoo.container.plugin.util.JarFiles.withInputStream; @@ -90,32 +91,43 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { @Parameter(alias = "X-Jersey-Binding") private String jerseyBinding = null; - public void execute() throws MojoExecutionException, MojoFailureException { + public void execute() throws MojoExecutionException { try { Artifacts.ArtifactSet artifactSet = Artifacts.getArtifacts(project); warnOnUnsupportedArtifacts(artifactSet.getNonJarArtifacts()); - AnalyzeBundle.PublicPackages publicPackagesFromProvidedJars = AnalyzeBundle.publicPackagesAggregated( + // Packages from Export-Package and Global-Package headers in provided scoped jars + AnalyzeBundle.PublicPackages publicPackagesFromProvidedJars = publicPackagesAggregated( artifactSet.getJarArtifactsProvided().stream().map(Artifact::getFile).collect(Collectors.toList())); - PackageTally includedJarPackageTally = definedPackages(artifactSet.getJarArtifactsToInclude()); - PackageTally projectPackageTally = analyzeProjectClasses(); - PackageTally pluginPackageTally = projectPackageTally.combine(includedJarPackageTally); + // Packages from Export-Package headers in provided scoped jars + Set<String> exportedPackagesFromProvidedDeps = publicPackagesFromProvidedJars.exportedPackageNames(); - Set<String> definedPackages = new HashSet<>(projectPackageTally.definedPackages()); - definedPackages.addAll(includedJarPackageTally.definedPackages()); + // Packaged defined in this project's code + PackageTally projectPackages = getProjectClassesTally(); - warnIfPackagesDefinedOverlapsGlobalPackages(definedPackages, publicPackagesFromProvidedJars.globals); + // Packages defined in compile scoped jars + PackageTally compileJarsPackages = definedPackages(artifactSet.getJarArtifactsToInclude()); - if (getLog().isDebugEnabled()) { - getLog().debug("Referenced packages = " + pluginPackageTally.referencedPackages()); - getLog().debug("Defined packages = " + pluginPackageTally.definedPackages()); - getLog().debug("Exported packages of dependencies = " + publicPackagesFromProvidedJars.exports.stream() - .map(e -> "(" + e.getPackageNames().toString() + ", " + e.version().orElse("")).collect(Collectors.joining(", "))); + // The union of packages in the project and compile scoped jars + PackageTally includedPackages = projectPackages.combine(compileJarsPackages); + + warnIfPackagesDefinedOverlapsGlobalPackages(includedPackages.definedPackages(), publicPackagesFromProvidedJars.globals); + logDebugPackageSets(publicPackagesFromProvidedJars, includedPackages); + + if (hasJdiscCoreProvided(artifactSet.getJarArtifactsProvided())) { + // jdisc_core being provided guarantees that log output does not contain its exported packages + logMissingPackages(exportedPackagesFromProvidedDeps, projectPackages, compileJarsPackages, includedPackages); + } else { + getLog().warn("This project does not have jdisc_core as provided dependency, so the " + + "generated 'Import-Package' OSGi header may be missing important packages."); } + logOverlappingPackages(projectPackages, exportedPackagesFromProvidedDeps); + logUnnecessaryPackages(compileJarsPackages, exportedPackagesFromProvidedDeps); - Map<String, Import> calculatedImports = ImportPackages.calculateImports(pluginPackageTally.referencedPackages(), - pluginPackageTally.definedPackages(), ExportPackages.exportsByPackageName(publicPackagesFromProvidedJars.exports)); + Map<String, Import> calculatedImports = calculateImports(includedPackages.referencedPackages(), + includedPackages.definedPackages(), + exportsByPackageName(publicPackagesFromProvidedJars.exports)); Map<String, Optional<String>> manualImports = emptyToNone(importPackage).map(GenerateOsgiManifestMojo::getManualImports) .orElseGet(HashMap::new); @@ -123,17 +135,74 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { calculatedImports.remove(packageName); } createManifestFile(new File(project.getBuild().getOutputDirectory()), manifestContent(project, - artifactSet.getJarArtifactsToInclude(), manualImports, calculatedImports.values(), pluginPackageTally)); + artifactSet.getJarArtifactsToInclude(), manualImports, calculatedImports.values(), includedPackages)); } catch (Exception e) { - throw new MojoExecutionException("Failed generating osgi manifest.", e); + throw new MojoExecutionException("Failed generating osgi manifest", e); + } + } + + private void logDebugPackageSets(AnalyzeBundle.PublicPackages publicPackagesFromProvidedJars, PackageTally includedPackages) { + if (getLog().isDebugEnabled()) { + getLog().debug("Referenced packages = " + includedPackages.referencedPackages()); + getLog().debug("Defined packages = " + includedPackages.definedPackages()); + getLog().debug("Exported packages of dependencies = " + publicPackagesFromProvidedJars.exports.stream() + .map(e -> "(" + e.getPackageNames().toString() + ", " + e.version().orElse("")).collect(Collectors.joining(", "))); + } + } + + private boolean hasJdiscCoreProvided(List<Artifact> providedArtifacts) { + return providedArtifacts.stream().anyMatch(artifact -> artifact.getArtifactId().equals("jdisc_core")); + } + + private void logMissingPackages(Set<String> exportedPackagesFromProvidedJars, + PackageTally projectPackages, + PackageTally compileJarPackages, + PackageTally includedPackages) { + + Set<String> definedAndExportedPackages = Sets.union(includedPackages.definedPackages(), exportedPackagesFromProvidedJars); + + Set<String> missingProjectPackages = projectPackages.referencedPackagesMissingFrom(definedAndExportedPackages); + if (! missingProjectPackages.isEmpty()) { + getLog().warn("Packages unavailable runtime are referenced from project classes " + + "(annotations can usually be ignored): " + missingProjectPackages); + } + + Set<String> missingCompilePackages = compileJarPackages.referencedPackagesMissingFrom(definedAndExportedPackages); + if (! missingCompilePackages.isEmpty()) { + getLog().info("Packages unavailable runtime are referenced from compile scoped jars " + + "(annotations can usually be ignored): " + missingCompilePackages); + } + } + + private void logOverlappingPackages(PackageTally projectPackages, + Set<String> exportedPackagesFromProvidedDeps) { + Set<String> overlappingProjectPackages = Sets.intersection(projectPackages.definedPackages(), exportedPackagesFromProvidedDeps); + if (! overlappingProjectPackages.isEmpty()) { + getLog().warn("Project classes use the following packages that are already defined in provided scoped dependencies: " + + overlappingProjectPackages); + } + } + + /* + * This mostly detects packages re-exported via composite bundles like jdisc_core and container-disc. + * An artifact can only be represented once, either in compile or provided scope. So if the project + * adds an artifact in compile scope that we deploy as a pre-installed bundle, we won't see the same + * artifact as provided via container-dev and hence can't detect the duplicate packages. + */ + private void logUnnecessaryPackages(PackageTally compileJarsPackages, + Set<String> exportedPackagesFromProvidedDeps) { + Set<String> unnecessaryPackages = Sets.intersection(compileJarsPackages.definedPackages(), exportedPackagesFromProvidedDeps); + if (! unnecessaryPackages.isEmpty()) { + getLog().info("Compile scoped jars contain the following packages that are most likely " + + "available from jdisc runtime: " + unnecessaryPackages); } } private static void warnIfPackagesDefinedOverlapsGlobalPackages(Set<String> internalPackages, List<String> globalPackages) throws MojoExecutionException { Set<String> overlap = Sets.intersection(internalPackages, new HashSet<>(globalPackages)); - if (overlap.isEmpty() == false) { + if (! overlap.isEmpty()) { throw new MojoExecutionException( "The following packages are both global and included in the bundle:\n " + String.join("\n ", overlap)); } @@ -173,7 +242,7 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { Pair.of("WebInfUrl", webInfUrl), // Pair.of("Import-Package", importPackage), // Pair.of("Export-Package", exportPackage))) { - if (element.getValue() != null && element.getValue().isEmpty() == false) { + if (element.getValue() != null && ! element.getValue().isEmpty()) { ret.put(element.getKey(), element.getValue()); } } @@ -232,7 +301,7 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { } private void warnOnUnsupportedArtifacts(Collection<Artifact> nonJarArtifacts) { - List<Artifact> unsupportedArtifacts = nonJarArtifacts.stream().filter(a -> "pom".equals(a.getType()) == false) + List<Artifact> unsupportedArtifacts = nonJarArtifacts.stream().filter(a -> ! a.getType().equals("pom")) .collect(Collectors.toList()); unsupportedArtifacts.forEach(artifact -> getLog() @@ -240,7 +309,7 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { artifact.getId(), artifact.getType()))); } - private PackageTally analyzeProjectClasses() { + private PackageTally getProjectClassesTally() { File outputDirectory = new File(project.getBuild().getOutputDirectory()); List<ClassFileMetaData> analyzedClasses = allDescendantFiles(outputDirectory).filter(file -> file.getName().endsWith(".class")) @@ -258,7 +327,7 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { List<ClassFileMetaData> analyzedClasses = new ArrayList<>(); for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) { JarEntry entry = entries.nextElement(); - if (entry.isDirectory() == false && entry.getName().endsWith(".class")) { + if (! entry.isDirectory() && entry.getName().endsWith(".class")) { analyzedClasses.add(analyzeClass(jarFile, entry)); } } @@ -305,10 +374,7 @@ public class GenerateOsgiManifestMojo extends AbstractMojo { } private static Optional<String> emptyToNone(String str) { - return Optional.ofNullable(str).map(String::trim).filter(s -> s.isEmpty() == false); + return Optional.ofNullable(str).map(String::trim).filter(s -> ! s.isEmpty()); } - private static boolean isClassToAnalyze(String name) { - return name.endsWith(".class") && name.endsWith("module-info.class") == false; - } } diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java index b58248ec4a6..a3031cb7ad7 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java @@ -74,8 +74,9 @@ public class ImportPackages { } } - public static Map<String, Import> calculateImports(Set<String> referencedPackages, Set<String> implementedPackages, - Map<String, ExportPackages.Export> exportedPackages) { + public static Map<String, Import> calculateImports(Set<String> referencedPackages, + Set<String> implementedPackages, + Map<String, ExportPackages.Export> exportedPackages) { Map<String, Import> ret = new HashMap<>(); for (String undefinedPackage : Sets.difference(referencedPackages, implementedPackages)) { ExportPackages.Export export = exportedPackages.get(undefinedPackage); diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JdkPackages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JdkPackages.java new file mode 100644 index 00000000000..0786272bc70 --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JdkPackages.java @@ -0,0 +1,39 @@ +package com.yahoo.container.plugin.util; + +import java.net.URL; + +/** + * @author gjoranv + */ +public class JdkPackages { + + /** + * Returns a boolean indicating (via best effort) if the given package is part of the JDK. + */ + public static boolean isJdkPackage(String pkg) { + return hasJdkExclusivePrefix(pkg) + || isResourceInPlatformClassLoader(pkg); // TODO: must be a class, not a package, due to module encapsulation + } + + private static boolean isResourceInPlatformClassLoader(String klass) { + String klassAsPath = klass.replaceAll("\\.", "/") + ".class"; + URL resource = getPlatformClassLoader().getResource(klassAsPath); + return !(resource == null); + } + + private static ClassLoader getPlatformClassLoader() { + ClassLoader platform = JdkPackages.class.getClassLoader().getParent(); + + // Will fail upon changes in classloader hierarchy between JDK versions + assert (platform.getName().equals("platform")); + + return platform; + } + + private static boolean hasJdkExclusivePrefix(String pkg) { + return pkg.startsWith("java.") + || pkg.startsWith("sun."); + } + +} + diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java index 8cccf0598ab..a09604b842b 100644 --- a/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java +++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java @@ -11,8 +11,10 @@ import org.junit.rules.ExpectedException; import java.io.File; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static com.yahoo.container.plugin.classanalysis.TestUtilities.throwableMessage; import static org.hamcrest.CoreMatchers.containsString; @@ -28,6 +30,7 @@ import static org.junit.Assert.assertThat; */ public class AnalyzeBundleTest { private final List<ExportPackages.Export> exports; + private final Set<String> exportedPackageNames; private final Map<String, ExportPackages.Export> exportsByPackageName; File jarDir = new File("src/test/resources/jar"); @@ -37,6 +40,7 @@ public class AnalyzeBundleTest { File simple = new File(jarDir, "simple1.jar"); PublicPackages pp = AnalyzeBundle.publicPackagesAggregated(Arrays.asList(notOsgi, simple)); this.exports = pp.exports; + this.exportedPackageNames = pp.exportedPackageNames(); this.exportsByPackageName = ExportPackages.exportsByPackageName(exports); } @@ -55,6 +59,11 @@ public class AnalyzeBundleTest { assertThat(exportsByPackageName.keySet(), hasItem("com.yahoo.sample.exported.package")); } + @Test + public void exported_class_names_can_be_retrieved() { + assertThat(exportedPackageNames, is(new HashSet<>(exports.get(0).getPackageNames()))); + } + @Rule public ExpectedException exception = ExpectedException.none(); diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/PackageTallyTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/PackageTallyTest.java new file mode 100644 index 00000000000..e80562c2c13 --- /dev/null +++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/PackageTallyTest.java @@ -0,0 +1,24 @@ +// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.classanalysis; + +import org.junit.Test; + +import java.util.Set; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author gjoranv + */ +public class PackageTallyTest { + + @Test + public void referenced_packages_missing_from_are_detected() { + PackageTally tally = new PackageTally(emptyMap(), Set.of("p1", "java.util", "com.yahoo.api.annotations", "missing")); + Set<String> missingPackages = tally.referencedPackagesMissingFrom(Set.of("p1")); + assertThat(missingPackages, is(Set.of("missing"))); + } + +} diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java index 23960323a31..b76151c790e 100644 --- a/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java +++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java @@ -109,8 +109,9 @@ public class ImportPackageTest { is("com.yahoo.exported;version=\"[1.2.3,2)\"")); } - private static Set<Import> calculateImports(Set<String> referencedPackages, Set<String> implementedPackages, - Map<String, Export> exportedPackages) { + private static Set<Import> calculateImports(Set<String> referencedPackages, + Set<String> implementedPackages, + Map<String, Export> exportedPackages) { return new HashSet<>(ImportPackages.calculateImports(referencedPackages, implementedPackages, exportedPackages).values()); } diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/CasWriteFailed.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/CasWriteFailed.java new file mode 100644 index 00000000000..c62d8d4bcd5 --- /dev/null +++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/CasWriteFailed.java @@ -0,0 +1,23 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.clustercontroller.core.database; + +/** + * Exception used to signal that a write intended to overwrite a value previously + * read encountered an underlying version conflict during an atomic compare-and-swap + * operation. This generally means that another node has written to the value since + * we last read it, and that the information we hold may be stale. + * + * Upon receiving such an exception, the caller should no longer assume it holds + * up-to-date information and should drop and roles that build on top of such an + * assumption (such as leadership sessions). + */ +public class CasWriteFailed extends RuntimeException { + + public CasWriteFailed(String message) { + super(message); + } + + public CasWriteFailed(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/Database.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/Database.java index c98e362c2d2..4b0461200a4 100644 --- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/Database.java +++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/Database.java @@ -46,7 +46,12 @@ public abstract class Database { * store the version in the database, such that if another fleetcontroller takes over as master it will use a * higher version system state. * + * Precondition: retrieveLatestSystemStateVersion() MUST have been called at least once prior to calling + * this method. + * * @return True if request succeeded. False if not. + * @throws CasWriteFailed if the expected version of the znode did not match what was actually stored in the DB. + * In this case, the write has NOT been applied. */ public abstract boolean storeLatestSystemStateVersion(int version) throws InterruptedException; @@ -84,6 +89,17 @@ public abstract class Database { */ public abstract Map<Node, Long> retrieveStartTimestamps() throws InterruptedException; + /** + * Stores the last published cluster state bundle synchronously into ZooKeeper. + * + * Precondition: retrieveLastPublishedStateBundle() MUST have been called at least once prior to calling + * this method. + * + * @return true if the write is known to have been successful, false otherwise. If false is returned, the + * write may or may not have taken place. + * @throws CasWriteFailed if the expected version of the znode did not match what was actually stored in the DB. + * In this case, the write has NOT been applied. + */ public abstract boolean storeLastPublishedStateBundle(ClusterStateBundle stateBundle) throws InterruptedException; public abstract ClusterStateBundle retrieveLastPublishedStateBundle() throws InterruptedException; diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/DatabaseHandler.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/DatabaseHandler.java index f30b86130c2..b9d02f16090 100644 --- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/DatabaseHandler.java +++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/DatabaseHandler.java @@ -105,8 +105,7 @@ public class DatabaseHandler { } public void shutdown(FleetController fleetController) { - reset(); - fleetController.lostDatabaseConnection(); + relinquishDatabaseConnectivity(fleetController); } public boolean isClosed() { return database == null || database.isClosed(); } @@ -232,68 +231,85 @@ public class DatabaseHandler { didWork = true; connect(context.getCluster(), currentTime); } - synchronized (databaseMonitor) { - if (database == null || database.isClosed()) { - return didWork; - } - if (pendingStore.masterVote != null) { - didWork = true; - log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Attempting to store master vote " - + pendingStore.masterVote + " into zookeeper."); - if (database.storeMasterVote(pendingStore.masterVote)) { - log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Managed to store master vote " - + pendingStore.masterVote + " into zookeeper."); - currentlyStored.masterVote = pendingStore.masterVote; - pendingStore.masterVote = null; - } else { - log.log(LogLevel.WARNING, "Fleetcontroller " + nodeIndex + ": Failed to store master vote"); + try { + synchronized (databaseMonitor) { + if (database == null || database.isClosed()) { return didWork; } + didWork |= performZooKeeperWrites(); } - if (pendingStore.lastSystemStateVersion != null) { - didWork = true; - log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex - + ": Attempting to store last system state version " + pendingStore.lastSystemStateVersion - + " into zookeeper."); - // TODO guard version write with a CaS predicated on the version we last read/wrote. - // TODO Drop leadership status if there is a mismatch, as it implies we're racing with another leader. - if (database.storeLatestSystemStateVersion(pendingStore.lastSystemStateVersion)) { - currentlyStored.lastSystemStateVersion = pendingStore.lastSystemStateVersion; - pendingStore.lastSystemStateVersion = null; - } else { - return didWork; - } + } catch (CasWriteFailed e) { + log.log(LogLevel.WARNING, String.format("CaS write to ZooKeeper failed, another controller " + + "has likely taken over ownership: %s", e.getMessage())); + // Clear DB and master election state. This shall trigger a full re-fetch of all + // version and election-related metadata. + relinquishDatabaseConnectivity(context.getFleetController()); + } + return didWork; + } + + private void relinquishDatabaseConnectivity(FleetController fleetController) { + reset(); + fleetController.lostDatabaseConnection(); + } + + private boolean performZooKeeperWrites() throws InterruptedException { + boolean didWork = false; + if (pendingStore.masterVote != null) { + didWork = true; + log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Attempting to store master vote " + + pendingStore.masterVote + " into zookeeper."); + if (database.storeMasterVote(pendingStore.masterVote)) { + log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Managed to store master vote " + + pendingStore.masterVote + " into zookeeper."); + currentlyStored.masterVote = pendingStore.masterVote; + pendingStore.masterVote = null; + } else { + log.log(LogLevel.WARNING, "Fleetcontroller " + nodeIndex + ": Failed to store master vote"); + return true; } - if (pendingStore.startTimestamps != null) { - didWork = true; - log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Attempting to store " - + pendingStore.startTimestamps.size() + " start timestamps into zookeeper."); - if (database.storeStartTimestamps(pendingStore.startTimestamps)) { - currentlyStored.startTimestamps = pendingStore.startTimestamps; - pendingStore.startTimestamps = null; - } else { - return didWork; - } + } + if (pendingStore.lastSystemStateVersion != null) { + didWork = true; + log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + + ": Attempting to store last system state version " + pendingStore.lastSystemStateVersion + + " into zookeeper."); + if (database.storeLatestSystemStateVersion(pendingStore.lastSystemStateVersion)) { + currentlyStored.lastSystemStateVersion = pendingStore.lastSystemStateVersion; + pendingStore.lastSystemStateVersion = null; + } else { + return true; } - if (pendingStore.wantedStates != null) { - didWork = true; - log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Attempting to store " - + pendingStore.wantedStates.size() + " wanted states into zookeeper."); - if (database.storeWantedStates(pendingStore.wantedStates)) { - currentlyStored.wantedStates = pendingStore.wantedStates; - pendingStore.wantedStates = null; - } else { - return didWork; - } + } + if (pendingStore.startTimestamps != null) { + didWork = true; + log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Attempting to store " + + pendingStore.startTimestamps.size() + " start timestamps into zookeeper."); + if (database.storeStartTimestamps(pendingStore.startTimestamps)) { + currentlyStored.startTimestamps = pendingStore.startTimestamps; + pendingStore.startTimestamps = null; + } else { + return true; } - if (pendingStore.clusterStateBundle != null) { - didWork = true; - if (database.storeLastPublishedStateBundle(pendingStore.clusterStateBundle)) { - currentlyStored.clusterStateBundle = pendingStore.clusterStateBundle; - pendingStore.clusterStateBundle = null; - } else { - return true; - } + } + if (pendingStore.wantedStates != null) { + didWork = true; + log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Attempting to store " + + pendingStore.wantedStates.size() + " wanted states into zookeeper."); + if (database.storeWantedStates(pendingStore.wantedStates)) { + currentlyStored.wantedStates = pendingStore.wantedStates; + pendingStore.wantedStates = null; + } else { + return true; + } + } + if (pendingStore.clusterStateBundle != null) { + didWork = true; + if (database.storeLastPublishedStateBundle(pendingStore.clusterStateBundle)) { + currentlyStored.clusterStateBundle = pendingStore.clusterStateBundle; + pendingStore.clusterStateBundle = null; + } else { + return true; } } return didWork; diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/ZooKeeperDatabase.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/ZooKeeperDatabase.java index 77ca7e12711..880f2cdaf19 100644 --- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/ZooKeeperDatabase.java +++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/database/ZooKeeperDatabase.java @@ -35,6 +35,10 @@ public class ZooKeeperDatabase extends Database { private final int nodeIndex; private final MasterDataGatherer masterDataGatherer; private boolean reportErrors = true; + // Expected ZK znode versions. Note: these are _not_ -1 as that would match anything. + // We expect the caller to invoke the load methods prior to calling any store methods. + private int lastKnownStateBundleZNodeVersion = -2; + private int lastKnownStateVersionZNodeVersion = -2; public void stopErrorReporting() { reportErrors = false; @@ -196,10 +200,14 @@ public class ZooKeeperDatabase extends Database { byte data[] = Integer.toString(version).getBytes(utf8); try{ log.log(LogLevel.INFO, String.format("Fleetcontroller %d: Storing new cluster state version in ZooKeeper: %d", nodeIndex, version)); - session.setData(zooKeeperRoot + "latestversion", data, -1); + var stat = session.setData(zooKeeperRoot + "latestversion", data, lastKnownStateVersionZNodeVersion); + lastKnownStateVersionZNodeVersion = stat.getVersion(); return true; } catch (InterruptedException e) { throw (InterruptedException) new InterruptedException("Interrupted").initCause(e); + } catch (KeeperException.BadVersionException e) { + throw new CasWriteFailed(String.format("version mismatch in cluster state version znode (expected %d): %s", + lastKnownStateVersionZNodeVersion, e.getMessage()), e); } catch (Exception e) { maybeLogExceptionWarning(e, "Failed to store latest system state version used " + version); return false; @@ -209,10 +217,13 @@ public class ZooKeeperDatabase extends Database { public Integer retrieveLatestSystemStateVersion() throws InterruptedException { Stat stat = new Stat(); try{ - log.log(LogLevel.DEBUG, "Fleetcontroller " + nodeIndex + ": Fetching latest cluster state at '" + zooKeeperRoot + "latestversion'"); + log.log(LogLevel.DEBUG, () -> String.format("Fleetcontroller %d: Fetching latest cluster state at '%slatestversion'", + nodeIndex, zooKeeperRoot)); byte[] data = session.getData(zooKeeperRoot + "latestversion", false, stat); + lastKnownStateVersionZNodeVersion = stat.getVersion(); final Integer versionNumber = Integer.valueOf(new String(data, utf8)); - log.log(LogLevel.INFO, String.format("Fleetcontroller %d: Read cluster state version %d from ZooKeeper", nodeIndex, versionNumber)); + log.log(LogLevel.INFO, String.format("Fleetcontroller %d: Read cluster state version %d from ZooKeeper " + + "(znode version %d)", nodeIndex, versionNumber, stat.getVersion())); return versionNumber; } catch (InterruptedException e) { throw (InterruptedException) new InterruptedException("Interrupted").initCause(e); @@ -336,12 +347,16 @@ public class ZooKeeperDatabase extends Database { EnvelopedClusterStateBundleCodec envelopedBundleCodec = new SlimeClusterStateBundleCodec(); byte[] encodedBundle = envelopedBundleCodec.encodeWithEnvelope(stateBundle); try{ - log.log(LogLevel.DEBUG, () -> String.format("Fleetcontroller %d: Storing published state bundle %s at '%spublished_state_bundle'", - nodeIndex, stateBundle, zooKeeperRoot)); - // TODO CAS on expected zknode version - session.setData(zooKeeperRoot + "published_state_bundle", encodedBundle, -1); + log.log(LogLevel.DEBUG, () -> String.format("Fleetcontroller %d: Storing published state bundle %s at " + + "'%spublished_state_bundle' with expected znode version %d", + nodeIndex, stateBundle, zooKeeperRoot, lastKnownStateBundleZNodeVersion)); + var stat = session.setData(zooKeeperRoot + "published_state_bundle", encodedBundle, lastKnownStateBundleZNodeVersion); + lastKnownStateBundleZNodeVersion = stat.getVersion(); } catch (InterruptedException e) { throw (InterruptedException) new InterruptedException("Interrupted").initCause(e); + } catch (KeeperException.BadVersionException e) { + throw new CasWriteFailed(String.format("version mismatch in cluster state bundle znode (expected %d): %s", + lastKnownStateBundleZNodeVersion, e.getMessage()), e); } catch (Exception e) { maybeLogExceptionWarning(e, "Failed to store last published cluster state bundle in ZooKeeper"); return false; @@ -354,6 +369,7 @@ public class ZooKeeperDatabase extends Database { Stat stat = new Stat(); try { byte[] data = session.getData(zooKeeperRoot + "published_state_bundle", false, stat); + lastKnownStateBundleZNodeVersion = stat.getVersion(); if (data != null && data.length != 0) { EnvelopedClusterStateBundleCodec envelopedBundleCodec = new SlimeClusterStateBundleCodec(); return envelopedBundleCodec.decodeWithEnvelope(data); diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperDatabaseTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperDatabaseTest.java index e5200b63a7e..6062f45c4de 100644 --- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperDatabaseTest.java +++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperDatabaseTest.java @@ -1,9 +1,12 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.core; +import com.yahoo.vespa.clustercontroller.core.database.CasWriteFailed; import com.yahoo.vespa.clustercontroller.core.database.Database; import com.yahoo.vespa.clustercontroller.core.database.ZooKeeperDatabase; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.io.IOException; import java.time.Duration; @@ -14,6 +17,9 @@ import static org.mockito.Mockito.mock; public class ZooKeeperDatabaseTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private static class Fixture implements AutoCloseable { final ZooKeeperTestServer zkServer; ClusterFixture clusterFixture; @@ -53,9 +59,8 @@ public class ZooKeeperDatabaseTest { public void can_store_and_load_cluster_state_bundle_from_database() throws Exception { try (Fixture f = new Fixture()) { f.createDatabase(); - ClusterStateBundle bundleToStore = ClusterStateBundleUtil.makeBundle("distributor:2 storage:2", - StateMapping.of("default", "distributor:2 storage:2 .0.s:d"), - StateMapping.of("upsidedown", "distributor:2 .0.s:d storage:2")); + f.db().retrieveLastPublishedStateBundle(); // Must be called once prior to prime last known znode version + ClusterStateBundle bundleToStore = dummyBundle(); f.db().storeLastPublishedStateBundle(bundleToStore); ClusterStateBundle bundleReceived = f.db().retrieveLastPublishedStateBundle(); @@ -63,6 +68,32 @@ public class ZooKeeperDatabaseTest { } } + private static ClusterStateBundle dummyBundle() { + return ClusterStateBundleUtil.makeBundle("distributor:2 storage:2", + StateMapping.of("default", "distributor:2 storage:2 .0.s:d"), + StateMapping.of("upsidedown", "distributor:2 .0.s:d storage:2")); + } + + @Test + public void storing_cluster_state_bundle_with_mismatching_expected_znode_version_throws_exception() throws Exception { + expectedException.expect(CasWriteFailed.class); + expectedException.expectMessage("version mismatch in cluster state bundle znode (expected -2)"); + try (Fixture f = new Fixture()) { + f.createDatabase(); + f.db().storeLastPublishedStateBundle(dummyBundle()); + } + } + + @Test + public void storing_cluster_state_version_with_mismatching_expected_znode_version_throws_exception() throws Exception { + expectedException.expect(CasWriteFailed.class); + expectedException.expectMessage("version mismatch in cluster state version znode (expected -2)"); + try (Fixture f = new Fixture()) { + f.createDatabase(); + f.db().storeLatestSystemStateVersion(12345); + } + } + @Test public void empty_state_bundle_is_returned_if_no_bundle_already_stored_in_database() throws Exception { try (Fixture f = new Fixture()) { diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 3b4bc1c2c5c..4e47409a7a0 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -59,6 +59,7 @@ public interface ModelContext { boolean useAdaptiveDispatch(); // TODO: Remove temporary default implementation default Optional<TlsSecrets> tlsSecrets() { return Optional.empty(); } + default boolean enableGroupingSessionCache() { return false; } } } diff --git a/config-model/src/main/java/com/yahoo/config/model/ConfigModelUtils.java b/config-model/src/main/java/com/yahoo/config/model/ConfigModelUtils.java deleted file mode 100644 index d80df441798..00000000000 --- a/config-model/src/main/java/com/yahoo/config/model/ConfigModelUtils.java +++ /dev/null @@ -1,49 +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.config.model; - -import java.io.Serializable; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.yahoo.text.Lowercase.toLowerCase; - -/** - * Utilities for config models - * - * @author gjoranv - */ -// TODO: Split this into appropriate classes, or move to ConfigModel superclass -public class ConfigModelUtils implements Serializable { - - private static final long serialVersionUID = 1L; - - public static Pattern hourNmin = Pattern.compile("(\\d\\d):(\\d\\d)"); - - public static Map<String, Integer> day2int; - static { - day2int = new HashMap<>(); - day2int.put("sunday", 0); - day2int.put("monday", 1); - day2int.put("tuesday", 2); - day2int.put("wednesday", 3); - day2int.put("thursday", 4); - day2int.put("friday", 5); - day2int.put("saturday", 6); - } - - /** Parses a 24 hour clock that must be the five characters ##:## to an int stating minutes after midnight. */ - public static int getTimeOfDay(String time) { - Matcher m = ConfigModelUtils.hourNmin.matcher(time); - if (m.matches()) { - return Integer.parseInt(m.group(1)) * 60 + Integer.parseInt(m.group(2)); - } - throw new IllegalArgumentException("The string '" + time + "' is not in ##:## format."); - } - - /** Parses a day of week name in english to an int, where 0 is sunday, 6 saturday. */ - public static int getDayOfWeek(String day) { - return ConfigModelUtils.day2int.get(toLowerCase(day)); - } - -} diff --git a/config-model/src/main/java/com/yahoo/config/model/MapConfigModelRegistry.java b/config-model/src/main/java/com/yahoo/config/model/MapConfigModelRegistry.java index 9178e4beca9..d94a9c1580a 100644 --- a/config-model/src/main/java/com/yahoo/config/model/MapConfigModelRegistry.java +++ b/config-model/src/main/java/com/yahoo/config/model/MapConfigModelRegistry.java @@ -5,20 +5,19 @@ import com.google.inject.Inject; import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.config.model.builder.xml.ConfigModelBuilder; import com.yahoo.config.model.builder.xml.ConfigModelId; -import com.yahoo.log.LogLevel; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; -import java.util.*; -import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * @author Ulf Lilleengen - * @since 5.1 */ public class MapConfigModelRegistry extends ConfigModelRegistry { - private static final Logger log = Logger.getLogger(MapConfigModelRegistry.class.getPackage().getName()); private final List<ConfigModelBuilder> builders; /** diff --git a/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java b/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java index 22dce91140d..9aeb5c40b28 100644 --- a/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java +++ b/config-model/src/main/java/com/yahoo/config/model/producer/UserConfigRepo.java @@ -1,9 +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.config.model.producer; -import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.log.LogLevel; -import com.yahoo.vespa.config.*; +import com.yahoo.vespa.config.ConfigDefinitionKey; +import com.yahoo.vespa.config.ConfigPayload; +import com.yahoo.vespa.config.ConfigPayloadBuilder; import java.util.LinkedHashMap; import java.util.Map; @@ -14,7 +14,6 @@ import java.util.Set; * how the user configs are stored, and defines the methods to retrieve user configs and merge the repo with others. * * @author Ulf Lilleengen - * @since 5.1 */ public class UserConfigRepo { private final Map<ConfigDefinitionKey, ConfigPayloadBuilder> userConfigsMap; @@ -46,10 +45,6 @@ public class UserConfigRepo { this.userConfigsMap = map; } - public UserConfigRepo(UserConfigRepo userConfigRepo) { - this.userConfigsMap = userConfigRepo.userConfigsMap; - } - public ConfigPayloadBuilder get(ConfigDefinitionKey key) { return userConfigsMap.get(key); } diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/Host.java b/config-model/src/main/java/com/yahoo/config/model/provision/Host.java index 8c8debbae43..c8d92220db3 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/Host.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/Host.java @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList; import com.yahoo.component.Version; import com.yahoo.config.provision.Flavor; -import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java index b381a5f28db..60435decb04 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java @@ -341,7 +341,7 @@ public class MockApplicationPackage implements ApplicationPackage { } @Override - public Reader createReader() throws FileNotFoundException { + public Reader createReader() { try { if ( ! exists()) throw new FileNotFoundException("File '" + file + "' does not exist"); return IOUtils.createReader(file, "UTF-8"); @@ -352,7 +352,7 @@ public class MockApplicationPackage implements ApplicationPackage { } @Override - public InputStream createInputStream() throws FileNotFoundException { + public InputStream createInputStream() { try { if ( ! exists()) throw new FileNotFoundException("File '" + file + "' does not exist"); return new BufferedInputStream(new FileInputStream(file)); diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java index 13d4064f55f..74ab4504136 100644 --- a/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java +++ b/config-model/src/main/java/com/yahoo/config/model/test/MockRoot.java @@ -7,7 +7,6 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.ConfigModelContext; import com.yahoo.config.model.ConfigModelRepo; import com.yahoo.config.model.api.HostProvisioner; -import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.builder.xml.XmlHelper; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index d738929f721..7a34ff258b7 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -20,7 +20,6 @@ import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue; import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; import com.yahoo.tensor.TensorType; -import com.yahoo.tensor.evaluation.TypeContext; import com.yahoo.vespa.model.VespaModel; import java.io.File; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java index 6481d42446f..bf1939e2c3d 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java @@ -8,7 +8,6 @@ import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.document.DocumentTypeManager; import com.yahoo.io.IOUtils; import com.yahoo.io.reader.NamedReader; -import com.yahoo.search.query.profile.QueryProfile; import com.yahoo.search.query.profile.QueryProfileRegistry; import com.yahoo.search.query.profile.config.QueryProfileXMLReader; import com.yahoo.searchdefinition.derived.SearchOrderer; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java index da1ea85f2ea..f25dd0be7b2 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java @@ -78,7 +78,7 @@ public class DerivedConfiguration { if ( ! search.isDocumentsOnly()) { attributeFields = new AttributeFields(search); summaries = new Summaries(search, deployLogger); - summaryMap = new SummaryMap(search, summaries); + summaryMap = new SummaryMap(search); juniperrc = new Juniperrc(search); rankProfileList = new RankProfileList(search, search.rankingConstants(), attributeFields, rankProfileRegistry, queryProfiles, importedModels); indexingScript = new IndexingScript(search); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java index a807cc79d95..02b2a383318 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java @@ -10,8 +10,8 @@ import com.yahoo.searchdefinition.processing.NGramMatch; import com.yahoo.vespa.documentmodel.SummaryField; import com.yahoo.search.config.IndexInfoConfig; -import java.util.*; -import java.util.stream.Stream; +import java.util.Map; +import java.util.Set; /** * Per-index commands which should be applied to queries prior to searching diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java index dc6c17e425e..94d475cf519 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java @@ -4,7 +4,6 @@ package com.yahoo.searchdefinition.derived; import com.yahoo.document.DataType; import com.yahoo.document.PositionDataType; import com.yahoo.searchdefinition.Search; -import com.yahoo.searchdefinition.document.Attribute; import com.yahoo.searchdefinition.document.ImmutableSDField; import com.yahoo.vespa.configdefinition.IlscriptsConfig; import com.yahoo.vespa.configdefinition.IlscriptsConfig.Ilscript.Builder; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java index f88da34428d..78400666c36 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.derived; -import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.document.DataType; import com.yahoo.prelude.fastsearch.DocsumDefinitionSet; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java index 930d164ac4b..71bf05dbb6e 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java @@ -22,13 +22,12 @@ public class SummaryMap extends Derived implements SummarymapConfig.Producer { private Map<String,FieldResultTransform> resultTransforms = new java.util.LinkedHashMap<>(); - /** Crateate a summary map from a search definition */ - public SummaryMap(Search search, Summaries summaries) { - derive(search, summaries); + /** Creates a summary map from a search definition */ + SummaryMap(Search search) { + derive(search); } - protected void derive(Search search, Summaries summaries) { - // TODO: This should really derive from the 'summaries' argument. Bug? + protected void derive(Search search) { for (DocumentSummary documentSummary : search.getSummaries().values()) { derive(documentSummary); } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java index b7b796d8976..e3bbf78ec21 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java @@ -4,8 +4,6 @@ package com.yahoo.searchdefinition.derived.validation; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.derived.DerivedConfiguration; -import java.util.logging.Logger; - /** * @author mathiasm */ diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java index cae9b20b7b5..6df8a7998a6 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java @@ -1,11 +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.searchdefinition.document; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; - /** * The rank type of a field. For now this is just a container of a string name. * This class is immutable. diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java index c4e0f4cafef..8b523211471 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java @@ -463,33 +463,12 @@ public class SDField extends Field implements TypedKey, FieldOperationContainer, } } }.visit(indexingScript); - } else { - if (!getDataType().equals(PositionDataType.INSTANCE) && - !getDataType().equals(DataType.getArray(PositionDataType.INSTANCE)) && - hasAttributeExpression(exp)) - { - throw new IllegalArgumentException("For field '" + getName() + "': Setting attribute on a field that has struct or map sub-type(s) is not supported"); - } } for (SDField structField : getStructFields()) { structField.setIndexingScript(exp); } } - private static boolean hasAttributeExpression(ScriptExpression exp) { - var visitor = new ExpressionVisitor() { - boolean result = false; - @Override - protected void doVisit(Expression exp) { - if (exp instanceof AttributeExpression) { - result = true; - } - } - }; - visitor.visit(exp); - return visitor.result; - } - @Override public ScriptExpression getIndexingScript() { return indexingScript; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/RankProfileTransformContext.java b/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/RankProfileTransformContext.java index 630c8644eb1..c76b8536ea0 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/RankProfileTransformContext.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/expressiontransforms/RankProfileTransformContext.java @@ -3,9 +3,7 @@ package com.yahoo.searchdefinition.expressiontransforms; import ai.vespa.rankingexpression.importer.configmodelview.ImportedMlModels; import com.yahoo.search.query.profile.QueryProfileRegistry; -import com.yahoo.searchdefinition.MapEvaluationTypeContext; import com.yahoo.searchdefinition.RankProfile; -import com.yahoo.searchlib.rankingexpression.Reference; import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.searchlib.rankingexpression.transform.TransformContext; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java index 59dc4275e15..f0575f1f70f 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddAttributeTransformToSummaryOfImportedFields.java @@ -6,11 +6,9 @@ import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.document.ImmutableImportedComplexSDField; import com.yahoo.searchdefinition.document.ImmutableSDField; -import com.yahoo.searchdefinition.document.ImportedComplexField; import com.yahoo.vespa.documentmodel.SummaryField; import com.yahoo.vespa.documentmodel.SummaryTransform; import com.yahoo.vespa.model.container.search.QueryProfiles; -import com.yahoo.searchdefinition.document.ImmutableImportedSDField; import java.util.stream.Stream; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java index 6e089d6155b..e8c958f9609 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFields.java @@ -8,18 +8,13 @@ import com.yahoo.document.PositionDataType; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.document.Attribute; -import com.yahoo.searchdefinition.document.ImmutableImportedSDField; import com.yahoo.searchdefinition.document.ImmutableSDField; -import com.yahoo.searchdefinition.document.ImportedField; import com.yahoo.vespa.documentmodel.DocumentSummary; import com.yahoo.vespa.documentmodel.SummaryField; import com.yahoo.vespa.documentmodel.SummaryField.Source; import com.yahoo.vespa.documentmodel.SummaryTransform; import com.yahoo.vespa.model.container.search.QueryProfiles; -import java.util.LinkedList; -import java.util.List; - /* * Adjusts position summary fields by adding derived summary fields (.distance and .position) and setting summary * transform and source. diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java index b0129c0a836..fbe04881ce1 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java @@ -4,7 +4,6 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.Search; -import com.yahoo.searchdefinition.document.SDDocumentType; import com.yahoo.searchdefinition.document.SDField; import com.yahoo.vespa.indexinglanguage.ExpressionConverter; import com.yahoo.vespa.indexinglanguage.ExpressionVisitor; diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java index cd75f1f8df7..483fe2105a3 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java @@ -2,14 +2,9 @@ package com.yahoo.searchdefinition.processing.multifieldresolver; import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.language.Linguistics; -import com.yahoo.searchdefinition.Index; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.document.SDField; import com.yahoo.searchdefinition.document.Stemming; -import com.yahoo.searchdefinition.processing.BuiltInFieldSets; -import com.yahoo.vespa.indexinglanguage.expressions.*; -import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig; import java.util.List; import java.util.logging.Level; diff --git a/config-model/src/main/java/com/yahoo/vespa/documentmodel/SearchDef.java b/config-model/src/main/java/com/yahoo/vespa/documentmodel/SearchDef.java index 5905ee3c3b5..1ff674da439 100644 --- a/config-model/src/main/java/com/yahoo/vespa/documentmodel/SearchDef.java +++ b/config-model/src/main/java/com/yahoo/vespa/documentmodel/SearchDef.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.documentmodel; import com.yahoo.document.DataType; -import com.yahoo.document.DocumentType; import com.yahoo.document.DocumentTypeManager; import java.util.HashMap; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducerRoot.java b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducerRoot.java index 2d4b8e9201f..b30aa5aeca6 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducerRoot.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ConfigProducerRoot.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model; import com.yahoo.config.ConfigInstance; -import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.vespa.model.admin.Admin; import com.yahoo.vespa.model.filedistribution.FileDistributor; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/Host.java b/config-model/src/main/java/com/yahoo/vespa/model/Host.java index 1aff926a8c4..a952d63526b 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/Host.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/Host.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model; import java.io.File; -import java.io.IOException; import java.util.Objects; import com.yahoo.cloud.config.SentinelConfig; @@ -68,7 +67,7 @@ public final class Host extends AbstractConfigProducer<AbstractConfigProducer<?> } @Override - public void writeFiles(File directory) throws IOException { + public void writeFiles(File directory) { } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java b/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java index 225d0bbcb11..69b692e3ad9 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostPorts.java @@ -6,7 +6,6 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.provision.NetworkPorts; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java b/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java index 8b47c03f4a4..6da8df60161 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostResource.java @@ -6,7 +6,6 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.api.HostInfo; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.Flavor; -import com.yahoo.config.provision.NetworkPorts; import javax.annotation.Nullable; import java.util.ArrayList; @@ -18,7 +17,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.logging.Level; import java.util.stream.Collectors; /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java index 0dde5c99d4a..09e67ed96cb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java @@ -8,7 +8,6 @@ import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostSpec; -import com.yahoo.config.provision.NetworkPorts; import com.yahoo.config.provision.ProvisionLogger; import java.net.UnknownHostException; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java index 03c8055dd12..d31c411a39e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/Admin.java @@ -119,8 +119,6 @@ public class Admin extends AbstractConfigProducer implements Serializable { return configservers; } - public void removeSlobroks() { slobroks.clear(); } - /** Returns an immutable list of the slobroks in this */ public List<Slobrok> getSlobroks() { return Collections.unmodifiableList(slobroks); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java index f8f515cb609..458bd5903fc 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java @@ -3,13 +3,15 @@ package com.yahoo.vespa.model.admin; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; +import com.yahoo.container.handler.ThreadpoolConfig; +import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.Handler; /** - * @author gjoranv + * @author hmusum */ -public class LogserverContainerCluster extends ContainerCluster<LogserverContainer> { +public class LogserverContainerCluster extends ContainerCluster<LogserverContainer> implements ThreadpoolConfig.Producer { public LogserverContainerCluster(AbstractConfigProducer<?> parent, String name, DeployState deployState) { super(parent, name, name, deployState); @@ -21,6 +23,21 @@ public class LogserverContainerCluster extends ContainerCluster<LogserverContain @Override protected void doPrepare(DeployState deployState) { } + // Switch off verbose:gc, it's very noisy when Xms < Xmx + @Override + public void getConfig(QrStartConfig.Builder builder) { + super.getConfig(builder); + // This takes effect via vespa-start-container-daemon:configure_gcopts + builder.jvm.verbosegc(false); + } + + @Override + public void getConfig(ThreadpoolConfig.Builder builder) { + builder.maxthreads(10); + } + + protected boolean messageBusEnabled() { return false; } + private void addLogHandler() { Handler<?> logHandler = Handler.fromClassName(ContainerCluster.LOG_HANDLER_CLASS); logHandler.addServerBindings("*://*/logs"); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/ZooKeepersConfigProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/ZooKeepersConfigProvider.java index be3ec8a6a14..25dbd62e647 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/ZooKeepersConfigProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/ZooKeepersConfigProvider.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.model.admin; import com.yahoo.collections.CollectionUtil; import com.yahoo.cloud.config.ZookeepersConfig; -import com.yahoo.config.model.api.ConfigServerSpec; import java.util.ArrayList; import java.util.List; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerCluster.java index 61a42d1c1a6..8ba85047057 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerCluster.java @@ -8,7 +8,6 @@ import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.vespa.model.Service; import com.yahoo.vespa.model.admin.Configserver; import com.yahoo.vespa.model.container.Container; -import com.yahoo.vespa.model.container.ContainerCluster; import java.util.ArrayList; import java.util.Collection; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java index 277335fc3ea..2de4e5f5950 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainerCluster.java @@ -27,4 +27,6 @@ public class ClusterControllerContainerCluster extends ContainerCluster<ClusterC @Override protected void doPrepare(DeployState deployState) { } + protected boolean messageBusEnabled() { return false; } + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java index 34a7d3c16cf..091c5a3acb4 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java @@ -21,6 +21,7 @@ import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Zone; +import com.yahoo.container.handler.ThreadpoolConfig; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.model.VespaModel; @@ -63,7 +64,8 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC ApplicationDimensionsConfig.Producer, ConsumersConfig.Producer, MonitoringConfig.Producer, - QrStartConfig.Producer + QrStartConfig.Producer, + ThreadpoolConfig.Producer { public static final Logger log = Logger.getLogger(MetricsProxyContainerCluster.class.getName()); @@ -91,7 +93,6 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC this.parent = parent; applicationId = deployState.getProperties().applicationId(); - setMessageBusEnabled(false); setRpcServerEnabled(true); addDefaultHandlersExceptStatus(); @@ -151,6 +152,13 @@ public class MetricsProxyContainerCluster extends ContainerCluster<MetricsProxyC builder.jvm.verbosegc(false); } + @Override + public void getConfig(ThreadpoolConfig.Builder builder) { + builder.maxthreads(10); + } + + protected boolean messageBusEnabled() { return false; } + private MetricSet getAdditionalDefaultMetrics() { return getAdmin() .map(Admin::getAdditionalDefaultMetrics) diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java index a2275b17233..866f647a351 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; -import com.yahoo.documentmodel.NewDocumentType; import com.yahoo.vespa.model.VespaModel; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilder.java index e9abcfd87a9..d07f6c4e6fd 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilder.java @@ -3,17 +3,16 @@ package com.yahoo.vespa.model.builder.xml.dom; import com.yahoo.collections.Tuple2; import com.yahoo.config.ConfigurationRuntimeException; -import com.yahoo.config.codegen.CNode; -import com.yahoo.log.LogLevel; +import com.yahoo.vespa.config.ConfigDefinition; +import com.yahoo.vespa.config.ConfigDefinitionKey; +import com.yahoo.vespa.config.ConfigPayloadBuilder; import com.yahoo.yolean.Exceptions; import com.yahoo.text.XML; -import com.yahoo.vespa.config.*; import com.yahoo.vespa.config.util.ConfigUtils; import org.w3c.dom.Element; -import java.util.*; -import java.util.logging.Logger; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,8 +26,6 @@ import java.util.regex.Pattern; */ public class DomConfigPayloadBuilder { - private static final Logger log = Logger.getLogger(DomConfigPayloadBuilder.class.getPackage().toString()); - private static final Pattern namePattern = ConfigDefinition.namePattern; private static final Pattern namespacePattern = ConfigDefinition.namespacePattern; @@ -57,40 +54,30 @@ public class DomConfigPayloadBuilder { public static ConfigDefinitionKey parseConfigName(Element configE) { if (!configE.getNodeName().equals("config")) { - throw new ConfigurationRuntimeException("The root element must be 'config', but was '" - + configE.getNodeName() + "'."); + throw new ConfigurationRuntimeException("The root element must be 'config', but was '" + configE.getNodeName() + "'."); } + if (!configE.hasAttribute("name")) { throw new ConfigurationRuntimeException ("The 'config' element must have a 'name' attribute that matches the name of the config definition."); } - String xmlName = configE.getAttribute("name"); - final boolean xmlNamespaceAttributeExists = configE.hasAttribute("namespace"); - - String xmlNamespace = null; - // If name contains dots, rewrite to name and namespace - if (xmlName.contains(".")) { - Tuple2<String, String> t = ConfigUtils.getNameAndNamespaceFromString(xmlName); - xmlName = t.first; - xmlNamespace = t.second; - } else { - if (!xmlNamespaceAttributeExists) { - log.log(LogLevel.WARNING, "No namespace in 'config name=" + xmlName + "', please specify one"); - } + String elementString = configE.getAttribute("name"); + if (!elementString.contains(".")) { + throw new ConfigurationRuntimeException("The config name '" + elementString + + "' contains illegal characters. Only names with the pattern " + + namespacePattern.pattern() + "." + namePattern.pattern() + " are legal."); } + Tuple2<String, String> t = ConfigUtils.getNameAndNamespaceFromString(elementString); + String xmlName = t.first; + String xmlNamespace = t.second; + if (!validName(xmlName)) { throw new ConfigurationRuntimeException("The config name '" + xmlName + "' contains illegal characters. Only names with the pattern " + namePattern.toString() + " are legal."); } - if (xmlNamespace == null) { - xmlNamespace = configE.getAttribute("namespace"); - if (xmlNamespace == null || xmlNamespace.isEmpty()) { - xmlNamespace = CNode.DEFAULT_NAMESPACE; - } - } if (!validNamespace(xmlNamespace)) { throw new ConfigurationRuntimeException("The config namespace '" + xmlNamespace + "' contains illegal characters. Only namespaces with the pattern " + namespacePattern.toString() + " are legal."); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomV20ClientsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomV20ClientsBuilder.java index 74e3ac581c4..63661104dae 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomV20ClientsBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomV20ClientsBuilder.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.builder.xml.dom; -import com.yahoo.config.model.deploy.DeployState; import com.yahoo.text.XML; import com.yahoo.vespa.model.clients.Clients; import org.w3c.dom.Element; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java index 9b33568b61e..10979a21838 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/Clients.java @@ -4,14 +4,8 @@ package com.yahoo.vespa.model.clients; import com.yahoo.vespa.config.content.LoadTypeConfig; import com.yahoo.config.model.ConfigModel; import com.yahoo.config.model.ConfigModelContext; -import com.yahoo.config.model.ConfigModelRepo; -import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.documentapi.messagebus.loadtypes.LoadType; import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet; -import com.yahoo.vespa.model.container.ContainerCluster; - -import java.util.LinkedList; -import java.util.List; /** * This is the clients plugin for the Vespa model. It is responsible for creating diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java index 73a401e6a2a..473971c5e7a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java @@ -2,13 +2,18 @@ package com.yahoo.vespa.model.container; import com.yahoo.component.ComponentId; +import com.yahoo.component.ComponentSpecification; import com.yahoo.config.FileReference; import com.yahoo.config.application.api.ComponentInfo; import com.yahoo.config.model.api.TlsSecrets; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.container.BundlesConfig; +import com.yahoo.container.bundle.BundleInstantiationSpecification; +import com.yahoo.container.jdisc.ContainerMbusConfig; +import com.yahoo.container.jdisc.messagebus.MbusServerProvider; import com.yahoo.jdisc.http.ServletPathsConfig; +import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; import com.yahoo.vespa.defaults.Defaults; @@ -39,7 +44,8 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat BundlesConfig.Producer, RankProfilesConfig.Producer, RankingConstantsConfig.Producer, - ServletPathsConfig.Producer + ServletPathsConfig.Producer, + ContainerMbusConfig.Producer { private final Set<FileReference> applicationBundles = new LinkedHashSet<>(); @@ -50,11 +56,16 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat private ContainerModelEvaluation modelEvaluation; private Optional<TlsSecrets> tlsSecrets; + private final boolean enableGroupingSessionCache; + + private MbusParams mbusParams; + private boolean messageBusEnabled = true; public ApplicationContainerCluster(AbstractConfigProducer<?> parent, String subId, String name, DeployState deployState) { super(parent, subId, name, deployState); this.tlsSecrets = deployState.tlsSecrets(); + this.enableGroupingSessionCache = deployState.getProperties().enableGroupingSessionCache(); restApiGroup = new ConfigProducerGroup<>(this, "rest-api"); servletGroup = new ConfigProducerGroup<>(this, "servlet"); @@ -154,8 +165,60 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat if (modelEvaluation != null) modelEvaluation.getConfig(builder); } + @Override + public void getConfig(ContainerMbusConfig.Builder builder) { + if (mbusParams != null) { + if (mbusParams.maxConcurrentFactor != null) + builder.maxConcurrentFactor(mbusParams.maxConcurrentFactor); + if (mbusParams.documentExpansionFactor != null) + builder.documentExpansionFactor(mbusParams.documentExpansionFactor); + if (mbusParams.containerCoreMemory != null) + builder.containerCoreMemory(mbusParams.containerCoreMemory); + } + if (getDocproc() != null) + getDocproc().getConfig(builder); + } + public Optional<TlsSecrets> getTlsSecrets() { return tlsSecrets; } + public boolean enableGroupingSessionCache() { + return enableGroupingSessionCache; + } + + public void setMbusParams(MbusParams mbusParams) { + this.mbusParams = mbusParams; + } + + public final void setMessageBusEnabled(boolean messageBusEnabled) { this.messageBusEnabled = messageBusEnabled; } + + protected boolean messageBusEnabled() { return messageBusEnabled; } + + public void addMbusServer(ComponentId chainId) { + ComponentId serviceId = chainId.nestInNamespace(ComponentId.fromString("MbusServer")); + + addComponent( + new Component<>(new ComponentModel(new BundleInstantiationSpecification( + serviceId, + ComponentSpecification.fromString(MbusServerProvider.class.getName()), + null)))); + } + + public static class MbusParams { + // the amount of the maxpendingbytes to process concurrently, typically 0.2 (20%) + final Double maxConcurrentFactor; + + // the amount that documents expand temporarily when processing them + final Double documentExpansionFactor; + + // the space to reserve for container, docproc stuff (memory that cannot be used for processing documents), in MB + final Integer containerCoreMemory; + + public MbusParams(Double maxConcurrentFactor, Double documentExpansionFactor, Integer containerCoreMemory) { + this.maxConcurrentFactor = maxConcurrentFactor; + this.documentExpansionFactor = documentExpansionFactor; + this.containerCoreMemory = containerCoreMemory; + } + } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 47adac637ee..779d0eb028d 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -5,7 +5,6 @@ import com.yahoo.cloud.config.ClusterInfoConfig; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.cloud.config.RoutingProviderConfig; import com.yahoo.component.ComponentId; -import com.yahoo.component.ComponentSpecification; import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.docproc.DocprocConfig; import com.yahoo.config.docproc.SchemamappingConfig; @@ -20,10 +19,8 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.core.ApplicationMetadataConfig; import com.yahoo.container.core.document.ContainerDocumentConfig; import com.yahoo.container.handler.ThreadPoolProvider; -import com.yahoo.container.jdisc.ContainerMbusConfig; import com.yahoo.container.jdisc.JdiscBindingsConfig; import com.yahoo.container.jdisc.config.HealthMonitorConfig; -import com.yahoo.container.jdisc.messagebus.MbusServerProvider; import com.yahoo.container.jdisc.state.StateHandler; import com.yahoo.container.logging.AccessLog; import com.yahoo.container.usability.BindingsOverviewHandler; @@ -89,7 +86,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> ComponentsConfig.Producer, JdiscBindingsConfig.Producer, DocumentmanagerConfig.Producer, - ContainerMbusConfig.Producer, ContainerDocumentConfig.Producer, HealthMonitorConfig.Producer, ApplicationMetadataConfig.Producer, @@ -139,10 +135,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> private ContainerDocumentApi containerDocumentApi; private SecretStore secretStore; - // TODO: move all message-bus related fields/methods to ApplicationContainerCluster. No need for mbus for other clusters. - private MbusParams mbusParams; - private boolean messageBusEnabled = true; - private boolean rpcServerEnabled = true; private boolean httpServerEnabled = true; @@ -297,16 +289,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> protected abstract void doPrepare(DeployState deployState); - public void addMbusServer(ComponentId chainId) { - ComponentId serviceId = chainId.nestInNamespace(ComponentId.fromString("MbusServer")); - - addComponent( - new Component<>(new ComponentModel(new BundleInstantiationSpecification( - serviceId, - ComponentSpecification.fromString(MbusServerProvider.class.getName()), - null)))); - } - public String getName() { return name; } @@ -551,24 +533,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> if (containerSearch != null) containerSearch.getConfig(builder); } - @Override - public void getConfig(ContainerMbusConfig.Builder builder) { - if (mbusParams != null) { - if (mbusParams.maxConcurrentFactor != null) - builder.maxConcurrentFactor(mbusParams.maxConcurrentFactor); - if (mbusParams.documentExpansionFactor != null) - builder.documentExpansionFactor(mbusParams.documentExpansionFactor); - if (mbusParams.containerCoreMemory != null) - builder.containerCoreMemory(mbusParams.containerCoreMemory); - } - if (containerDocproc != null) - containerDocproc.getConfig(builder); - } - - public void setMbusParams(MbusParams mbusParams) { - this.mbusParams = mbusParams; - } - public void initialize(Map<String, AbstractSearchCluster> clusterMap) { if (containerSearch != null) containerSearch.connectSearchClusters(clusterMap); } @@ -660,10 +624,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> */ public Optional<Integer> getMemoryPercentage() { return Optional.ofNullable(memoryPercentage); } - public final void setMessageBusEnabled(boolean messageBusEnabled) { this.messageBusEnabled = messageBusEnabled; } - - boolean messageBusEnabled() { return messageBusEnabled; } - public final void setRpcServerEnabled(boolean rpcServerEnabled) { this.rpcServerEnabled = rpcServerEnabled; } boolean rpcServerEnabled() { return rpcServerEnabled; } @@ -677,21 +637,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> return "container cluster '" + getName() + "'"; } - public static class MbusParams { - // the amount of the maxpendingbytes to process concurrently, typically 0.2 (20%) - final Double maxConcurrentFactor; - - // the amount that documents expand temporarily when processing them - final Double documentExpansionFactor; - - // the space to reserve for container, docproc stuff (memory that cannot be used for processing documents), in MB - final Integer containerCoreMemory; - - public MbusParams(Double maxConcurrentFactor, Double documentExpansionFactor, Integer containerCoreMemory) { - this.maxConcurrentFactor = maxConcurrentFactor; - this.documentExpansionFactor = documentExpansionFactor; - this.containerCoreMemory = containerCoreMemory; - } - } + protected abstract boolean messageBusEnabled(); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/ContainerSubsystem.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/ContainerSubsystem.java index 3b0bb959a7b..3fa4db60195 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/ContainerSubsystem.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/ContainerSubsystem.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.model.container.component; -import com.yahoo.config.model.producer.AbstractConfigProducer; -import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.chain.Chains; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java index 011fcb7c3c7..e8d733f26a2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/Servlet.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.component; -import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.jdisc.http.ServletPathsConfig; import com.yahoo.osgi.provider.model.ComponentModel; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/ContainerDocproc.java b/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/ContainerDocproc.java index 57d5c203d32..5a251194d75 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/ContainerDocproc.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/ContainerDocproc.java @@ -3,13 +3,12 @@ package com.yahoo.vespa.model.container.docproc; import com.yahoo.collections.Pair; import com.yahoo.config.docproc.DocprocConfig; -import com.yahoo.container.jdisc.config.SessionConfig; -import com.yahoo.container.jdisc.ContainerMbusConfig; import com.yahoo.config.docproc.SchemamappingConfig; +import com.yahoo.container.jdisc.ContainerMbusConfig; +import com.yahoo.container.jdisc.config.SessionConfig; import com.yahoo.docproc.jdisc.messagebus.MbusRequestContext; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.ContainerSubsystem; -import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.HashMap; @@ -18,7 +17,6 @@ import java.util.Map; /** * @author einarmr * @author gjoranv - * @since 5.1.9 */ public class ContainerDocproc extends ContainerSubsystem<DocprocChains> implements @@ -70,18 +68,10 @@ public class ContainerDocproc extends ContainerSubsystem<DocprocChains> return preferLocalNode; } - public void setPreferLocalNode(boolean preferLocalNode) { - this.preferLocalNode = preferLocalNode; - } - public int getNumNodesPerClient() { return numNodesPerClient; } - public void setNumNodesPerClient(int numNodesPerClient) { - this.numNodesPerClient = numNodesPerClient; - } - @Override public void getConfig(ContainerMbusConfig.Builder builder) { builder.maxpendingcount(getMaxMessagesInQueue()); @@ -116,10 +106,6 @@ public class ContainerDocproc extends ContainerSubsystem<DocprocChains> } } - public ProcessingHandler<DocprocChains> getDocprocHandler() { - return getChains().getDocprocHandler(); - } - @Override public void getConfig(SchemamappingConfig.Builder builder) { Map<Pair<String, String>, String> allMappings = new HashMap<>(); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocprocChains.java b/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocprocChains.java index cafc65019ce..5d08a0a6998 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocprocChains.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocprocChains.java @@ -4,14 +4,14 @@ package com.yahoo.vespa.model.container.docproc; import com.yahoo.component.ComponentId; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.container.jdisc.config.SessionConfig; +import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.chain.Chains; import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; /** - * @author einarmr - * @since 5.1.9 + * @author Einar M R Rosenvinge */ public class DocprocChains extends Chains<DocprocChain> { private final ProcessingHandler<DocprocChains> docprocHandler; @@ -22,10 +22,6 @@ public class DocprocChains extends Chains<DocprocChain> { addComponent(docprocHandler); } - public ProcessingHandler<DocprocChains> getDocprocHandler() { - return docprocHandler; - } - private void addComponent(Component component) { if (!(getParent() instanceof ContainerCluster)) { return; @@ -35,13 +31,13 @@ public class DocprocChains extends Chains<DocprocChain> { public void addServersAndClientsForChains() { - if (getParent() instanceof ContainerCluster) { + if (getParent() instanceof ApplicationContainerCluster) { for (DocprocChain chain: getChainGroup().getComponents()) - addServerAndClientForChain((ContainerCluster) getParent(), chain); + addServerAndClientForChain((ApplicationContainerCluster) getParent(), chain); } } - private void addServerAndClientForChain(ContainerCluster cluster, DocprocChain docprocChain) { + private void addServerAndClientForChain(ApplicationContainerCluster cluster, DocprocChain docprocChain) { docprocHandler.addServerBindings("mbus://*/" + docprocChain.getSessionName()); cluster.addMbusServer(ComponentId.fromString(docprocChain.getSessionName())); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocumentProcessor.java b/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocumentProcessor.java index 92b636ed48b..62ea405179a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocumentProcessor.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/docproc/DocumentProcessor.java @@ -5,9 +5,6 @@ import com.yahoo.collections.Pair; import com.yahoo.vespa.model.container.component.chain.ChainedComponent; import com.yahoo.vespa.model.container.docproc.model.DocumentProcessorModel; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java index 9a67871e683..a03c5e1ec42 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java @@ -86,7 +86,7 @@ public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> impl } @Override - public void validate() throws Exception { + public void validate() { validate(bindings); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/processing/Processor.java b/config-model/src/main/java/com/yahoo/vespa/model/container/processing/Processor.java index 3ad6484aaec..efae7278b14 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/processing/Processor.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/processing/Processor.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model.container.processing; import com.yahoo.component.chain.model.ChainedComponentModel; -import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.vespa.model.container.component.chain.ChainedComponent; /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java index d2f7c5e4549..d111c423ca1 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java @@ -1,21 +1,20 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.search; +import com.yahoo.container.QrSearchersConfig; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.prelude.fastsearch.FS4ResourcePool; import com.yahoo.prelude.semantics.SemanticRulesConfig; +import com.yahoo.search.config.IndexInfoConfig; +import com.yahoo.search.pagetemplates.PageTemplatesConfig; +import com.yahoo.search.query.profile.config.QueryProfilesConfig; +import com.yahoo.vespa.configdefinition.IlscriptsConfig; import com.yahoo.vespa.model.container.ApplicationContainerCluster; -import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.ContainerSubsystem; import com.yahoo.vespa.model.container.search.searchchain.LocalProvider; import com.yahoo.vespa.model.container.search.searchchain.SearchChains; -import com.yahoo.search.config.IndexInfoConfig; -import com.yahoo.vespa.configdefinition.IlscriptsConfig; -import com.yahoo.container.QrSearchersConfig; -import com.yahoo.search.query.profile.config.QueryProfilesConfig; -import com.yahoo.search.pagetemplates.PageTemplatesConfig; import com.yahoo.vespa.model.search.AbstractSearchCluster; import com.yahoo.vespa.model.search.Dispatch; import com.yahoo.vespa.model.search.IndexedSearchCluster; @@ -41,6 +40,7 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> private final List<AbstractSearchCluster> systems = new LinkedList<>(); private final Options options; + private final boolean enableGroupingSessionCache; private QueryProfiles queryProfiles; private SemanticRules semanticRules; @@ -52,6 +52,7 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> // TODO: Should be added to container instead of cluster to get proper configId for qr config. cluster.addComponent(getFS4ResourcePool()); + this.enableGroupingSessionCache = cluster.enableGroupingSessionCache(); } private static Component<?, ComponentModel> getFS4ResourcePool() { @@ -98,7 +99,10 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains> @Override public void getConfig(QueryProfilesConfig.Builder builder) { - if (queryProfiles!=null) queryProfiles.getConfig(builder); + if (queryProfiles!=null) { + queryProfiles.getConfig(builder); + } + builder.enableGroupingSessionCache(enableGroupingSessionCache); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java index c7114178ad6..0abb0803405 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfiles.java @@ -11,10 +11,16 @@ import com.yahoo.search.query.profile.types.QueryProfileType; import com.yahoo.search.query.profile.config.QueryProfilesConfig; import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Level; -import java.util.stream.Collectors; /** * Owns the query profiles and query profile types to be handed to the qrs nodes. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfilesBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfilesBuilder.java index b832c1bbdcd..b653b0acf8d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfilesBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/QueryProfilesBuilder.java @@ -6,7 +6,6 @@ import com.yahoo.io.reader.NamedReader; import com.yahoo.search.query.profile.config.QueryProfileXMLReader; import com.yahoo.config.application.api.ApplicationPackage; -import java.util.Collections; import java.util.List; /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java index cb1d94717f6..aa820d1d898 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/searchchain/LocalProvider.java @@ -85,7 +85,7 @@ public class LocalProvider extends Provider implements } } - private void addProviderSearchers(LocalProviderSpec providerSpec) { + private void addProviderSearchers() { for (ChainedComponentModel searcherModel : LocalProviderSpec.searcherModels) { addInnerComponent(new Searcher<>(searcherModel)); } @@ -130,7 +130,7 @@ public class LocalProvider extends Provider implements FederationOptions federationOptions, LocalProviderSpec providerSpec) { super(specWithoutInnerSearchers, federationOptions); - addProviderSearchers(providerSpec); + addProviderSearchers(); this.providerSpec = providerSpec; } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 672ee0bb161..2bfb1da9dcb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -79,7 +79,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; -import java.util.logging.Level; import java.util.regex.Pattern; import java.util.stream.Collectors; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java index dc9372c463b..d25396c6a50 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentNode.java @@ -1,10 +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.model.content; -import com.yahoo.config.provision.Flavor; import com.yahoo.metrics.MetricsmanagerConfig; import com.yahoo.vespa.config.content.LoadTypeConfig; -import com.yahoo.vespa.config.content.StorFilestorConfig; import com.yahoo.vespa.config.content.core.StorCommunicationmanagerConfig; import com.yahoo.vespa.config.content.core.StorServerConfig; import com.yahoo.vespa.config.content.core.StorStatusConfig; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java index db7b4a88721..a6228af8991 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ContentSearchCluster.java @@ -233,7 +233,7 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot Optional<Tuning> tuning = Optional.ofNullable(this.tuning); if (element == null) { snode = SearchNode.create(parent, "" + node.getDistributionKey(), node.getDistributionKey(), spec, - clusterName, node, flushOnShutdown, tuning, parentGroup.getOwner().isHostedVespa()); + clusterName, node, flushOnShutdown, tuning, parentGroup.isHosted()); snode.setHostResource(node.getHostResource()); snode.initService(deployState.getDeployLogger()); @@ -283,9 +283,13 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot if (usesHierarchicDistribution()) { indexedCluster.setMaxNodesDownPerFixedRow((redundancy.effectiveFinalRedundancy() / groupToSpecMap.size()) - 1); } - indexedCluster.setSearchableCopies(redundancy.searchableCopies()); + indexedCluster.setSearchableCopies(redundancy.readyCopies()); } this.redundancy = redundancy; + for (SearchNode node : getSearchNodes()) { + node.setRedundancy(redundancy.finalRedundancy()); + node.setSearchableCopies(redundancy.readyCopies()); + } } private Optional<StreamingSearchCluster> findStreamingCluster(String docType) { @@ -306,7 +310,7 @@ public class ContentSearchCluster extends AbstractConfigProducer implements Prot @Override public void getConfig(ProtonConfig.Builder builder) { double visibilityDelay = hasIndexedCluster() ? getIndexed().getVisibilityDelay() : 0.0; - builder.feeding.concurrency(0.25); // As if specified 0.5 in services.xml + builder.feeding.concurrency(0.30); // As if specified 0.6 in services.xml boolean hasAnyNonIndexedCluster = false; for (NewDocumentType type : TopologicalDocumentTypeSorter.sort(documentDefinitions.values())) { ProtonConfig.Documentdb.Builder ddbB = new ProtonConfig.Documentdb.Builder(); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java index c721d3d48bc..7ab5ee9fd80 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionValidator.java @@ -33,15 +33,15 @@ public class IndexedHierarchicDistributionValidator { public void validate() throws Exception { validateThatWeHaveOneGroupLevel(); validateThatLeafGroupsHasEqualNumberOfNodes(); - validateThatLeafGroupsCountIsAFactorOfRedundancy(); + validateThatLeafGroupsCountIsAFactorOfRedundancy(clusterName, redundancy.effectiveFinalRedundancy(), rootGroup.getSubgroups().size()); validateThatRedundancyPerGroupIsEqual(); - validateThatReadyCopiesIsCompatibleWithRedundancy(rootGroup.getSubgroups().size()); + validateThatReadyCopiesIsCompatibleWithRedundancy(clusterName, redundancy.effectiveFinalRedundancy(), redundancy.effectiveReadyCopies(), rootGroup.getSubgroups().size()); } private void validateThatWeHaveOneGroupLevel() { for (StorageGroup group : rootGroup.getSubgroups()) { if (group.getSubgroups().size() > 0) { - throw new IllegalArgumentException(getErrorMsgPrefix() + "Expected all groups under root group '" + + throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected all groups under root group '" + rootGroup.getName() + "' to be leaf groups only containing nodes, but sub group '" + group.getName() + "' contains " + group.getSubgroups().size() + " sub groups."); } @@ -59,18 +59,18 @@ public class IndexedHierarchicDistributionValidator { } if (group.getNodes().size() != previousGroup.getNodes().size()) - throw new IllegalArgumentException(getErrorMsgPrefix() + "Expected leaf groups to contain an equal number of nodes, but leaf group '" + + throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected leaf groups to contain an equal number of nodes, but leaf group '" + previousGroup.getName() + "' contains " + previousGroup.getNodes().size() + " node(s) while leaf group '" + group.getName() + "' contains " + group.getNodes().size() + " node(s)."); previousGroup = group; } } - private void validateThatLeafGroupsCountIsAFactorOfRedundancy() { - if (redundancy.effectiveFinalRedundancy() % rootGroup.getSubgroups().size() != 0) { - throw new IllegalArgumentException(getErrorMsgPrefix() + "Expected number of leaf groups (" + - rootGroup.getSubgroups().size() + ") to be a factor of redundancy (" + - redundancy.effectiveFinalRedundancy() + "), but it is not."); + static public void validateThatLeafGroupsCountIsAFactorOfRedundancy(String clusterName, int totalRedundancy, int subGroups) { + if (totalRedundancy % subGroups != 0) { + throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected number of leaf groups (" + + subGroups + ") to be a factor of redundancy (" + + totalRedundancy + "), but it is not."); } } @@ -78,7 +78,7 @@ public class IndexedHierarchicDistributionValidator { int redundancyPerGroup = redundancy.effectiveFinalRedundancy() / rootGroup.getSubgroups().size(); String expPartitions = createDistributionPartitions(redundancyPerGroup, rootGroup.getSubgroups().size()); if (!rootGroup.getPartitions().get().equals(expPartitions)) { - throw new IllegalArgumentException(getErrorMsgPrefix() + "Expected redundancy per leaf group to be " + + throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected redundancy per leaf group to be " + redundancyPerGroup + ", but it is not according to distribution partitions '" + rootGroup.getPartitions().get() + "'. Expected distribution partitions should be '" + expPartitions + "'."); } @@ -98,20 +98,20 @@ public class IndexedHierarchicDistributionValidator { return sb.toString(); } - private void validateThatReadyCopiesIsCompatibleWithRedundancy(int groupCount) throws Exception { - if (redundancy.effectiveFinalRedundancy() % groupCount != 0) { - throw new Exception(getErrorMsgPrefix() + "Expected equal redundancy per group."); + static public void validateThatReadyCopiesIsCompatibleWithRedundancy(String clusterName, int totalRedundancy, int totalReadyCopies, int groupCount) { + if (totalRedundancy % groupCount != 0) { + throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected equal redundancy per group."); } - if (redundancy.effectiveReadyCopies() % groupCount != 0) { - throw new Exception(getErrorMsgPrefix() + "Expected equal amount of ready copies per group, but " + - redundancy.effectiveReadyCopies() + " ready copies is specified with " + groupCount + " groups"); + if (totalReadyCopies % groupCount != 0) { + throw new IllegalArgumentException(getErrorMsgPrefix(clusterName) + "Expected equal amount of ready copies per group, but " + + totalReadyCopies + " ready copies is specified with " + groupCount + " groups"); } - if (redundancy.effectiveReadyCopies() == 0) { - System.err.println(getErrorMsgPrefix() + "Warning. No ready copies configured. At least one is recommended."); + if (totalReadyCopies == 0) { + System.err.println(getErrorMsgPrefix(clusterName) + "Warning. No ready copies configured. At least one is recommended."); } } - private String getErrorMsgPrefix() { + static private String getErrorMsgPrefix(String clusterName) { return "In indexed content cluster '" + clusterName + "' using hierarchic distribution: "; } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/Redundancy.java b/config-model/src/main/java/com/yahoo/vespa/model/content/Redundancy.java index b21a0da0d57..aa9b04d084d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/Redundancy.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/Redundancy.java @@ -11,54 +11,36 @@ import com.yahoo.vespa.config.search.core.ProtonConfig; */ public class Redundancy implements StorDistributionConfig.Producer, ProtonConfig.Producer { + // This numbers are all per group as wanted numbers. private final int initialRedundancy ; private final int finalRedundancy; private final int readyCopies; - private int implicitGroups = 1; - private int explicitGroups = 1; + private final int groups; /** The total number of nodes available in this cluster (assigned when this becomes known) */ - private int totalNodes = 0; + private final int totalNodes; - public Redundancy(int initialRedundancy, int finalRedundancy, int readyCopies) { + public Redundancy(int initialRedundancy, int finalRedundancy, int readyCopies, int groups, int totalNodes) { this.initialRedundancy = initialRedundancy; this.finalRedundancy = finalRedundancy; this.readyCopies = readyCopies; + this.groups = groups; + this.totalNodes = totalNodes; } - /** - * Set the total number of nodes available in this cluster. - * This impacts the effective redundancy in the case where there are fewer nodes available than - * the requested redundancy. - */ - public void setTotalNodes(int totalNodes) { this.totalNodes = totalNodes; } + public int finalRedundancy() { return effectiveFinalRedundancy()/groups; } + public int readyCopies() { return effectiveReadyCopies()/groups; } + public int groups() { return groups; } + public int totalNodes() { return totalNodes; } - /** - * Sets the number of groups resulting from implicit setup (groups attribute) - * in this cluster. With implicit groups the redundancy settings are taken to be - * <i>per group</i> and are multiplied by this number to get the effective <i>total</i> - * values returned in the config. - */ - public void setImplicitGroups(int implicitGroups) { this.implicitGroups = implicitGroups; } - - public void setExplicitGroups(int explicitGroups) { this.explicitGroups = explicitGroups; } - - public int initialRedundancy() { return initialRedundancy; } - public int finalRedundancy() { return finalRedundancy; } - public int readyCopies() { return readyCopies; } - public int totalNodes() { - return totalNodes; - } - - public int effectiveInitialRedundancy() { return Math.min(totalNodes, initialRedundancy * implicitGroups); } - public int effectiveFinalRedundancy() { return Math.min(totalNodes, finalRedundancy * implicitGroups); } - public int effectiveReadyCopies() { return Math.min(totalNodes, readyCopies * implicitGroups); } + public int effectiveInitialRedundancy() { return Math.min(totalNodes, initialRedundancy * groups); } + public int effectiveFinalRedundancy() { return Math.min(totalNodes, finalRedundancy * groups); } + public int effectiveReadyCopies() { return Math.min(totalNodes, readyCopies * groups); } public boolean isEffectivelyGloballyDistributed() { return totalNodes == effectiveFinalRedundancy(); } - public int searchableCopies() { return readyCopies/(explicitGroups*implicitGroups); } @Override public void getConfig(StorDistributionConfig.Builder builder) { @@ -69,8 +51,8 @@ public class Redundancy implements StorDistributionConfig.Producer, ProtonConfig @Override public void getConfig(ProtonConfig.Builder builder) { ProtonConfig.Distribution.Builder distBuilder = new ProtonConfig.Distribution.Builder(); - distBuilder.redundancy(finalRedundancy/explicitGroups); - distBuilder.searchablecopies(searchableCopies()); + distBuilder.redundancy(finalRedundancy()); + distBuilder.searchablecopies(readyCopies()); builder.distribution(distBuilder); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ResourceLimits.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ResourceLimits.java index 67e5dcdd0ee..12ebde2276a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/ResourceLimits.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ResourceLimits.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model.content; import com.yahoo.vespa.config.search.core.ProtonConfig; -import com.yahoo.vespa.defaults.Defaults; import java.util.Optional; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java index 84132427427..4d1252a2618 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java @@ -15,6 +15,7 @@ import com.yahoo.vespa.model.builder.xml.dom.NodesSpecification; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.content.cluster.ContentCluster; +import com.yahoo.vespa.model.content.cluster.RedundancyBuilder; import com.yahoo.vespa.model.content.engines.PersistenceEngine; import java.util.ArrayList; @@ -37,7 +38,7 @@ public class StorageGroup { private final String index; private Optional<String> partitions; String name; - private final ContentCluster owner; + private final boolean isHosted; private final Optional<Long> mmapNoCoreLimit; private final Optional<Boolean> coreOnOOM; private final Optional<String> noVespaMalloc; @@ -51,7 +52,7 @@ public class StorageGroup { /** * Creates a storage group * - * @param owner the cluster this group belongs to + * @param isHosted true if this is in a hosted setup * @param name the name of this group * @param index the distribution-key index og this group * @param partitions the distribution strategy to use to distribute content to subgroups or empty @@ -59,12 +60,12 @@ public class StorageGroup { * (having nodes, not subgroups as children). * @param useCpuSocketAffinity whether processes should be started with socket affinity */ - private StorageGroup(ContentCluster owner, String name, String index, Optional<String> partitions, + private StorageGroup(boolean isHosted, String name, String index, Optional<String> partitions, boolean useCpuSocketAffinity, Optional<Long> mmapNoCoreLimit, Optional<Boolean> coreOnOOM, Optional<String> noVespaMalloc, Optional<String> vespaMalloc, Optional<String> vespaMallocDebug, Optional<String> vespaMallocDebugStackTrace) { - this.owner = owner; + this.isHosted = isHosted; this.index = index; this.name = name; this.partitions = partitions; @@ -76,8 +77,8 @@ public class StorageGroup { this.vespaMallocDebug = vespaMallocDebug; this.vespaMallocDebugStackTrace = vespaMallocDebugStackTrace; } - private StorageGroup(ContentCluster owner, String name, String index) { - this(owner, name, index, Optional.empty(), false, Optional.empty(),Optional.empty(), Optional.empty(), + private StorageGroup(boolean isHosted, String name, String index) { + this(isHosted, name, index, Optional.empty(), false, Optional.empty(),Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); } @@ -90,7 +91,7 @@ public class StorageGroup { /** Returns the nodes of this, or an empty list of it is not a leaf group */ public List<StorageNode> getNodes() { return nodes; } - public ContentCluster getOwner() { return owner; } + public boolean isHosted() { return isHosted; } /** Returns the index of this group, or null if it is the root group */ public String getIndex() { return index; } @@ -194,16 +195,14 @@ public class StorageGroup { public static class Builder { private final ModelElement clusterElement; - private final ContentCluster owner; private final ConfigModelContext context; - public Builder(ModelElement clusterElement, ContentCluster owner, ConfigModelContext context) { + public Builder(ModelElement clusterElement, ConfigModelContext context) { this.clusterElement = clusterElement; - this.owner = owner; this.context = context; } - public StorageGroup buildRootGroup(DeployState deployState) { + public StorageGroup buildRootGroup(DeployState deployState, RedundancyBuilder redundancyBuilder, ContentCluster owner) { Optional<ModelElement> group = Optional.ofNullable(clusterElement.child("group")); Optional<ModelElement> nodes = getNodes(clusterElement); @@ -212,12 +211,28 @@ public class StorageGroup { if (group.isPresent() && (group.get().stringAttribute("name") != null || group.get().integerAttribute("distribution-key") != null)) deployState.getDeployLogger().log(LogLevel.INFO, "'distribution-key' attribute on a content cluster's root group is ignored"); - GroupBuilder groupBuilder = collectGroup(group, nodes, null, null); - if (owner.isHostedVespa()) { - return groupBuilder.buildHosted(deployState, owner, Optional.empty()); - } else { - return groupBuilder.buildNonHosted(deployState, owner, Optional.empty()); + GroupBuilder groupBuilder = collectGroup(owner.isHosted(), group, nodes, null, null); + StorageGroup storageGroup = (owner.isHosted()) + ? groupBuilder.buildHosted(deployState, owner, Optional.empty()) + : groupBuilder.buildNonHosted(deployState, owner, Optional.empty()); + Redundancy redundancy = redundancyBuilder.build(owner.getName(), owner.isHosted(), storageGroup.subgroups.size(), storageGroup.getNumberOfLeafGroups(), storageGroup.countNodes()); + owner.setRedundancy(redundancy); + if (storageGroup.partitions.isEmpty() && (redundancy.groups() > 1)) { + storageGroup.partitions = Optional.of(computePartitions(redundancy.finalRedundancy(), redundancy.groups())); } + return storageGroup; + } + + /** This returns a partition string which specifies equal distribution between all groups */ + // TODO: Make a partitions object + static private String computePartitions(int redundancyPerGroup, int numGroups) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < numGroups - 1; ++i) { + sb.append(redundancyPerGroup); + sb.append("|"); + } + sb.append("*"); + return sb.toString(); } /** @@ -262,9 +277,6 @@ public class StorageGroup { if ( ! parent.isPresent() && subGroups.isEmpty() && nodeBuilders.isEmpty()) // no nodes or groups: create single node storageGroup.nodes.add(buildSingleNode(deployState, owner)); - - if ( ! parent.isPresent()) - owner.redundancy().setTotalNodes(storageGroup.countNodes()); return storageGroup; } @@ -297,19 +309,11 @@ public class StorageGroup { if (hostGroups.size() > 1) { if (parent.isPresent()) throw new IllegalArgumentException("Cannot specify groups using the groups attribute in nested content groups"); - owner.redundancy().setTotalNodes(hostMapping.size()); - - // Switch redundancy settings to meaning "per group" - owner.redundancy().setImplicitGroups(hostGroups.size()); - - // Compute partitions expression - int redundancyPerGroup = (int)Math.floor(owner.redundancy().effectiveFinalRedundancy() / hostGroups.size()); - storageGroup.partitions = Optional.of(computePartitions(redundancyPerGroup, hostGroups.size())); // create subgroups as returned from allocation for (Map.Entry<Optional<ClusterSpec.Group>, Map<HostResource, ClusterMembership>> hostGroup : hostGroups.entrySet()) { String groupIndex = String.valueOf(hostGroup.getKey().get().index()); - StorageGroup subgroup = new StorageGroup(owner, groupIndex, groupIndex); + StorageGroup subgroup = new StorageGroup(true, groupIndex, groupIndex); for (Map.Entry<HostResource, ClusterMembership> host : hostGroup.getValue().entrySet()) { subgroup.nodes.add(createStorageNode(deployState, owner, host.getKey(), subgroup, host.getValue())); } @@ -323,24 +327,10 @@ public class StorageGroup { for (GroupBuilder subGroup : subGroups) { storageGroup.subgroups.add(subGroup.buildHosted(deployState, owner, Optional.of(this))); } - if ( ! parent.isPresent()) - owner.redundancy().setTotalNodes(storageGroup.countNodes()); } return storageGroup; } - /** This returns a partition string which specifies equal distribution between all groups */ - // TODO: Make a partitions object - private String computePartitions(int redundancyPerGroup, int numGroups) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < numGroups - 1; ++i) { - sb.append(redundancyPerGroup); - sb.append("|"); - } - sb.append("*"); - return sb.toString(); - } - /** Collect hosts per group */ private Map<Optional<ClusterSpec.Group>, Map<HostResource, ClusterMembership>> collectAllocatedSubgroups(Map<HostResource, ClusterMembership> hostMapping) { Map<Optional<ClusterSpec.Group>, Map<HostResource, ClusterMembership>> hostsPerGroup = new LinkedHashMap<>(); @@ -387,9 +377,9 @@ public class StorageGroup { * <li>Neither element is present: Create a single node. * </ul> */ - private GroupBuilder collectGroup(Optional<ModelElement> groupElement, Optional<ModelElement> nodesElement, String name, String index) { + private GroupBuilder collectGroup(boolean isHosted, Optional<ModelElement> groupElement, Optional<ModelElement> nodesElement, String name, String index) { StorageGroup group = new StorageGroup( - owner, name, index, + isHosted, name, index, childAsString(groupElement, "distribution.partitions"), booleanAttributeOr(groupElement, VespaDomBuilder.CPU_SOCKET_AFFINITY_ATTRIB_NAME, false), childAsLong(groupElement, VespaDomBuilder.MMAP_NOCORE_LIMIT), @@ -399,7 +389,7 @@ public class StorageGroup { childAsString(groupElement, VespaDomBuilder.VESPAMALLOC_DEBUG), childAsString(groupElement, VespaDomBuilder.VESPAMALLOC_DEBUG_STACKTRACE)); - List<GroupBuilder> subGroups = groupElement.isPresent() ? collectSubGroups(group, groupElement.get()) : Collections.emptyList(); + List<GroupBuilder> subGroups = groupElement.isPresent() ? collectSubGroups(isHosted, group, groupElement.get()) : Collections.emptyList(); List<XmlNodeBuilder> explicitNodes = new ArrayList<>(); explicitNodes.addAll(collectExplicitNodes(groupElement)); @@ -450,7 +440,7 @@ public class StorageGroup { return nodes; } - private List<GroupBuilder> collectSubGroups(StorageGroup parentGroup, ModelElement parentGroupElement) { + private List<GroupBuilder> collectSubGroups(boolean isHosted, StorageGroup parentGroup, ModelElement parentGroupElement) { List<ModelElement> subGroupElements = parentGroupElement.subElements("group"); if (subGroupElements.size() > 1 && ! parentGroup.getPartitions().isPresent()) throw new IllegalArgumentException("'distribution' attribute is required with multiple subgroups"); @@ -461,7 +451,7 @@ public class StorageGroup { indexPrefix = parentGroup.index + "."; } for (ModelElement g : subGroupElements) { - subGroups.add(collectGroup(Optional.of(g), Optional.ofNullable(g.child("nodes")), g.stringAttribute("name"), + subGroups.add(collectGroup(isHosted, Optional.of(g), Optional.ofNullable(g.child("nodes")), g.stringAttribute("name"), indexPrefix + g.integerAttribute("distribution-key"))); } return subGroups; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java index 833afb67f58..48779657162 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java @@ -80,9 +80,9 @@ public class ContentCluster extends AbstractConfigProducer implements MessagetyperouteselectorpolicyConfig.Producer, BucketspacesConfig.Producer { - private String documentSelection; + private final String documentSelection; private ContentSearchCluster search; - private final boolean isHostedVespa; + private final boolean isHosted; private final Map<String, NewDocumentType> documentDefinitions; private final Set<NewDocumentType> globallyDistributedDocuments; private com.yahoo.vespa.model.content.StorageGroup rootGroup; @@ -91,9 +91,9 @@ public class ContentCluster extends AbstractConfigProducer implements private Redundancy redundancy; private ClusterControllerConfig clusterControllerConfig; private PersistenceEngine.PersistenceFactory persistenceFactory; - private String clusterName; + private final String clusterName; private Integer maxNodesPerMerge; - private Zone zone; + private final Zone zone; /** * If multitenant or a cluster controller was explicitly configured in this cluster: @@ -124,21 +124,20 @@ public class ContentCluster extends AbstractConfigProducer implements new SearchDefinitionBuilder().build(deployState.getDocumentModel().getDocumentManager(), documentsElement); String routingSelection = new DocumentSelectionBuilder().build(documentsElement); - Redundancy redundancy = new RedundancyBuilder().build(contentElement); + RedundancyBuilder redundancyBuilder = new RedundancyBuilder(contentElement); Set<NewDocumentType> globallyDistributedDocuments = new GlobalDistributionBuilder(documentDefinitions).build(documentsElement); ContentCluster c = new ContentCluster(context.getParentProducer(), getClusterName(contentElement), documentDefinitions, - globallyDistributedDocuments, routingSelection, redundancy, + globallyDistributedDocuments, routingSelection, deployState.zone(), deployState.isHosted()); c.clusterControllerConfig = new ClusterControllerConfig.Builder(getClusterName(contentElement), contentElement).build(deployState, c, contentElement.getXml()); c.search = new ContentSearchCluster.Builder(documentDefinitions, globallyDistributedDocuments).build(deployState, c, contentElement.getXml()); c.persistenceFactory = new EngineFactoryBuilder().build(contentElement, c); c.storageNodes = new StorageCluster.Builder().build(deployState, c, w3cContentElement); c.distributorNodes = new DistributorCluster.Builder(c).build(deployState, c, w3cContentElement); - c.rootGroup = new StorageGroup.Builder(contentElement, c, context).buildRootGroup(deployState); + c.rootGroup = new StorageGroup.Builder(contentElement, context).buildRootGroup(deployState, redundancyBuilder, c); validateThatGroupSiblingsAreUnique(c.clusterName, c.rootGroup); - redundancy.setExplicitGroups(c.getRootGroup().getNumberOfLeafGroups()); - c.search.handleRedundancy(redundancy); + c.search.handleRedundancy(c.redundancy); IndexedSearchCluster index = c.search.getIndexed(); if (index != null) { @@ -492,14 +491,13 @@ public class ContentCluster extends AbstractConfigProducer implements private ContentCluster(AbstractConfigProducer parent, String clusterName, Map<String, NewDocumentType> documentDefinitions, Set<NewDocumentType> globallyDistributedDocuments, - String routingSelection, Redundancy redundancy, Zone zone, boolean isHostedVespa) { + String routingSelection, Zone zone, boolean isHosted) { super(parent, clusterName); - this.isHostedVespa = isHostedVespa; + this.isHosted = isHosted; this.clusterName = clusterName; this.documentDefinitions = documentDefinitions; this.globallyDistributedDocuments = globallyDistributedDocuments; this.documentSelection = routingSelection; - this.redundancy = redundancy; this.zone = zone; } @@ -553,6 +551,10 @@ public class ContentCluster extends AbstractConfigProducer implements public final ContentSearchCluster getSearch() { return search; } public Redundancy redundancy() { return redundancy; } + public ContentCluster setRedundancy(Redundancy redundancy) { + this.redundancy = redundancy; + return this; + } @Override public void getConfig(MessagetyperouteselectorpolicyConfig.Builder builder) { @@ -639,14 +641,14 @@ public class ContentCluster extends AbstractConfigProducer implements } } - public boolean isHostedVespa() { - return isHostedVespa; + public boolean isHosted() { + return isHosted; } @Override public void validate() throws Exception { super.validate(); - if (search.usesHierarchicDistribution() && ! isHostedVespa) { + if (search.usesHierarchicDistribution() && !isHosted) { // validate manually configured groups new IndexedHierarchicDistributionValidator(search.getClusterName(), rootGroup, redundancy, search.getIndexed().getTuning().dispatch.policy).validate(); if (search.getIndexed().useMultilevelDispatchSetup()) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java index fe73fcc904b..bf579744efb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/RedundancyBuilder.java @@ -2,22 +2,22 @@ package com.yahoo.vespa.model.content.cluster; import com.yahoo.vespa.model.builder.xml.dom.ModelElement; +import com.yahoo.vespa.model.content.IndexedHierarchicDistributionValidator; import com.yahoo.vespa.model.content.Redundancy; /** * Builds redundancy config for a content cluster. */ public class RedundancyBuilder { + Integer initialRedundancy = 2; + Integer finalRedundancy = 3; + Integer readyCopies = 2; - Redundancy build(ModelElement clusterXml) { - Integer initialRedundancy = 2; - Integer finalRedundancy = 3; - Integer readyCopies = 2; - + RedundancyBuilder(ModelElement clusterXml) { ModelElement redundancyElement = clusterXml.child("redundancy"); if (redundancyElement != null) { initialRedundancy = redundancyElement.integerAttribute("reply-after"); - finalRedundancy = (int)redundancyElement.asLong(); + finalRedundancy = (int) redundancyElement.asLong(); if (initialRedundancy == null) { initialRedundancy = finalRedundancy; @@ -35,8 +35,16 @@ public class RedundancyBuilder { throw new IllegalArgumentException("Number of searchable copies can not be higher than final redundancy"); } } - - return new Redundancy(initialRedundancy, finalRedundancy, readyCopies); + } + public Redundancy build(String clusterName, boolean isHosted, int subGroups, int leafGroups, int totalNodes) { + if (isHosted) { + return new Redundancy(initialRedundancy, finalRedundancy, readyCopies, leafGroups, totalNodes); + } else { + subGroups = Math.max(1, subGroups); + IndexedHierarchicDistributionValidator.validateThatLeafGroupsCountIsAFactorOfRedundancy(clusterName, finalRedundancy, subGroups); + IndexedHierarchicDistributionValidator.validateThatReadyCopiesIsCompatibleWithRedundancy(clusterName, finalRedundancy, readyCopies, subGroups); + return new Redundancy(initialRedundancy/subGroups, finalRedundancy/subGroups, readyCopies/subGroups, subGroups, totalNodes); + } } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/engines/ProtonProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/content/engines/ProtonProvider.java index ff3b4891146..c02f8b2b737 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/engines/ProtonProvider.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/engines/ProtonProvider.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.model.content.engines; import com.yahoo.vespa.config.content.core.StorServerConfig; import com.yahoo.vespa.model.content.StorageNode; -import com.yahoo.vespa.model.search.SearchNode; /** * @author baldersheim diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/IntegrityCheckerProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/IntegrityCheckerProducer.java index 46d621981d4..4fa117ad9e7 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/IntegrityCheckerProducer.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/IntegrityCheckerProducer.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model.content.storagecluster; import com.yahoo.vespa.config.content.core.StorIntegritycheckerConfig; -import com.yahoo.config.model.ConfigModelUtils; import com.yahoo.vespa.model.builder.xml.dom.ModelElement; import com.yahoo.vespa.model.content.cluster.ContentCluster; @@ -21,9 +20,6 @@ public class IntegrityCheckerProducer implements StorIntegritycheckerConfig.Prod private Integer stopTime; private String weeklyCycle; - IntegrityCheckerProducer() { - } - IntegrityCheckerProducer(Integer startTime, Integer stopTime, String weeklyCycle) { this.startTime = startTime; this.stopTime = stopTime; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java b/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java index e12cc60b041..3e70ac4705e 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java @@ -31,7 +31,6 @@ import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; import com.yahoo.searchlib.rankingexpression.rule.TensorFunctionNode; import com.yahoo.tensor.Tensor; import com.yahoo.tensor.TensorType; -import com.yahoo.tensor.evaluation.TypeContext; import com.yahoo.tensor.functions.Generate; import com.yahoo.tensor.functions.Join; import com.yahoo.tensor.functions.Reduce; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java b/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java index b29ed0fc25b..74b9e7309c8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/DocumentDatabase.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.model.search; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.search.config.IndexInfoConfig; -import com.yahoo.searchdefinition.RankingConstant; import com.yahoo.searchdefinition.derived.DerivedConfiguration; import com.yahoo.vespa.config.search.AttributesConfig; import com.yahoo.vespa.config.search.ImportedFieldsConfig; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java index ee82d0cd719..1d804ba3b27 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexedSearchCluster.java @@ -378,13 +378,14 @@ public class IndexedSearchCluster extends SearchCluster public void setMaxNodesDownPerFixedRow(int value) { maxNodesDownPerFixedRow = value; } - int getSearchableCopies() { + public int getSearchableCopies() { return searchableCopies; } public void setSearchableCopies(int searchableCopies) { this.searchableCopies = searchableCopies; } + public void setDispatchSpec(DispatchSpec dispatchSpec) { if (dispatchSpec.getNumDispatchGroups() != null) { this.dispatchSpec = new DispatchSpec.Builder().setGroups diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingDocprocChain.java b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingDocprocChain.java index df5f2e844af..df44a429f9c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingDocprocChain.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingDocprocChain.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.search; -import com.yahoo.collections.Pair; import com.yahoo.component.ComponentId; import com.yahoo.component.ComponentSpecification; import com.yahoo.component.chain.Phase; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingProcessor.java b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingProcessor.java index a23191e7689..d3d2c8873eb 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingProcessor.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/IndexingProcessor.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.search; -import com.yahoo.collections.Pair; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.component.ComponentId; import com.yahoo.component.ComponentSpecification; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java b/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java index 8ed18299b7b..3260cf3a680 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/NodeFlavorTuning.java @@ -16,9 +16,14 @@ public class NodeFlavorTuning implements ProtonConfig.Producer { static long MB = 1024 * 1024; static long GB = MB * 1024; private final Flavor nodeFlavor; + private final int redundancy; + private final int searchableCopies; - public NodeFlavorTuning(Flavor nodeFlavor) { + + public NodeFlavorTuning(Flavor nodeFlavor, int redundancy, int searchableCopies) { this.nodeFlavor = nodeFlavor; + this.redundancy = redundancy; + this.searchableCopies = searchableCopies; } @Override @@ -41,7 +46,7 @@ public class NodeFlavorTuning implements ProtonConfig.Producer { ProtonConfig.Documentdb dbCfg = builder.build(); if (dbCfg.mode() != ProtonConfig.Documentdb.Mode.Enum.INDEX) { long numDocs = (long)nodeFlavor.getMinMainMemoryAvailableGb()*GB/64L; - builder.allocation.initialnumdocs(numDocs); + builder.allocation.initialnumdocs(numDocs/Math.max(searchableCopies, redundancy)); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchColumn.java b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchColumn.java deleted file mode 100644 index b45db0d1dc3..00000000000 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchColumn.java +++ /dev/null @@ -1,27 +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.model.search; - -import com.yahoo.config.model.producer.AbstractConfigProducer; - -import java.util.LinkedList; -import java.util.List; - -/** - * @author Simon Thoresen Hult - */ -public class SearchColumn extends AbstractConfigProducer { - - // All search nodes contained in this column, these also exist as child config producers. - private final List<SearchNode> nodes = new LinkedList<>(); - - public SearchColumn(SearchCluster parent, String name, int index) { - super(parent, name); - } - - /** @return The number of rows in this column. */ - public int getNumRows() { return nodes.size(); } - - /** @return All search nodes contained in this column. */ - public List<SearchNode> getSearchNodes() { return nodes; } - -} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java index e255da5b487..21882ee640a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/search/SearchNode.java @@ -54,6 +54,8 @@ public class SearchNode extends AbstractService implements private final boolean flushOnShutdown; private NodeSpec nodeSpec; private int distributionKey; + private int redundancy = 1; + private int searchableCopies = 1; private final String clusterName; private TransactionLogServer tls; private AbstractService serviceLayerService; @@ -140,6 +142,12 @@ public class SearchNode extends AbstractService implements private String getBaseDir() { return getDefaults().underVespaHome("var/db/vespa/search/cluster." + getClusterName()) + "/n" + distributionKey; } + public void setSearchableCopies(int searchableCopies) { + this.searchableCopies = searchableCopies; + } + public void setRedundancy(int redundancy) { + this.redundancy = redundancy; + } public void updatePartition(int partitionId) { nodeSpec = new NodeSpec(nodeSpec.groupIndex(), partitionId); @@ -267,7 +275,7 @@ public class SearchNode extends AbstractService implements } if (getHostResource() != null && getHostResource().getFlavor().isPresent()) { Flavor nodeFlavor = getHostResource().getFlavor().get(); - NodeFlavorTuning nodeFlavorTuning = new NodeFlavorTuning(nodeFlavor); + NodeFlavorTuning nodeFlavorTuning = new NodeFlavorTuning(nodeFlavor, redundancy, searchableCopies); nodeFlavorTuning.getConfig(builder); if (tuning.isPresent()) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/utils/Duration.java b/config-model/src/main/java/com/yahoo/vespa/model/utils/Duration.java index 0b1e23510fe..c55316186d2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/utils/Duration.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/utils/Duration.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.model.utils; import java.util.HashMap; import java.util.Map; -import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java b/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java index 8995fcbca99..46d41b1e0b0 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/utils/FileSender.java @@ -20,8 +20,6 @@ import java.util.*; */ public class FileSender implements Serializable { - public enum FileType {FILE, URI}; - /** * Send the given file to all given services. * diff --git a/config-model/src/main/resources/schema/common.rnc b/config-model/src/main/resources/schema/common.rnc index 3e33227b064..c5690f9c915 100644 --- a/config-model/src/main/resources/schema/common.rnc +++ b/config-model/src/main/resources/schema/common.rnc @@ -41,7 +41,7 @@ OptionalDedicatedNodes = element nodes { GenericConfig = element config { attribute name { text }, - attribute namespace { text }?, + attribute namespace { text }?, # TODO: Remove in Vespa 8 attribute version { text }?, anyElement + } diff --git a/config-model/src/test/cfg/admin/userconfigs/functiontest-defaultvalues.xml b/config-model/src/test/cfg/admin/userconfigs/functiontest-defaultvalues.xml index 7da7d788ca1..298d6676354 100644 --- a/config-model/src/test/cfg/admin/userconfigs/functiontest-defaultvalues.xml +++ b/config-model/src/test/cfg/admin/userconfigs/functiontest-defaultvalues.xml @@ -1,6 +1,6 @@ <?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. --> -<config name="function-test"> +<config name="test.function-test"> <bool_val>false</bool_val> <int_val>5</int_val> <long_val>1234567890123</long_val> diff --git a/config-model/src/test/cfg/admin/userconfigs/whitespace-test.xml b/config-model/src/test/cfg/admin/userconfigs/whitespace-test.xml index 68576786376..956cba1c9ff 100644 --- a/config-model/src/test/cfg/admin/userconfigs/whitespace-test.xml +++ b/config-model/src/test/cfg/admin/userconfigs/whitespace-test.xml @@ -1,6 +1,6 @@ <?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. --> -<config name="function-test"> +<config name="test.function-test"> <stringVal> This is a string that contains different kinds of whitespace </stringVal> </config> diff --git a/config-model/src/test/cfg/application/app_genericservices/services.xml b/config-model/src/test/cfg/application/app_genericservices/services.xml index 6c7cad10e5d..9ae7f122e81 100644 --- a/config-model/src/test/cfg/application/app_genericservices/services.xml +++ b/config-model/src/test/cfg/application/app_genericservices/services.xml @@ -3,11 +3,11 @@ <services version="1.0"> <service version="1.0" name="myservice" command="mycmd1.sh"> - <config name="myconfig"> + <config name="a.myconfig"> <mysetting>bar</mysetting> </config> <node hostalias="node1"> - <config name="myconfig"> + <config name="a.myconfig"> <mysetting>baz</mysetting> </config> </node> @@ -17,11 +17,11 @@ </service> <service version="1.0" name="myotherservice" command="/home/vespa/bin/mycmd2.sh --ytest $FOO_BAR"> - <config name="myconfig"> + <config name="a.myconfig"> <mysetting>bar2</mysetting> </config> <node hostalias="node3"> - <config name="myconfig"> + <config name="a.myconfig"> <mysetting>baz2</mysetting> </config> </node> diff --git a/config-model/src/test/java/com/yahoo/config/model/ApplicationPackageTester.java b/config-model/src/test/java/com/yahoo/config/model/ApplicationPackageTester.java index fe64a69b311..ab321ac5835 100644 --- a/config-model/src/test/java/com/yahoo/config/model/ApplicationPackageTester.java +++ b/config-model/src/test/java/com/yahoo/config/model/ApplicationPackageTester.java @@ -48,8 +48,4 @@ public class ApplicationPackageTester { return new ApplicationPackageTester(applicationPackageDir, true); } - public static ApplicationPackageTester createWithoutValidation(String applicationPackageDir) { - return new ApplicationPackageTester(applicationPackageDir, false); - } - } diff --git a/config-model/src/test/java/com/yahoo/config/model/MapConfigModelRegistryTest.java b/config-model/src/test/java/com/yahoo/config/model/MapConfigModelRegistryTest.java index 93d71bb3f43..9847ad09198 100644 --- a/config-model/src/test/java/com/yahoo/config/model/MapConfigModelRegistryTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/MapConfigModelRegistryTest.java @@ -6,7 +6,6 @@ import com.yahoo.config.model.builder.xml.ConfigModelId; import org.junit.Test; import org.w3c.dom.Element; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; diff --git a/config-model/src/test/java/com/yahoo/config/model/QrserverAndGatewayPortAllocationTest.java b/config-model/src/test/java/com/yahoo/config/model/QrserverAndGatewayPortAllocationTest.java index 8fa2d1e5e76..ac776d3cf78 100644 --- a/config-model/src/test/java/com/yahoo/config/model/QrserverAndGatewayPortAllocationTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/QrserverAndGatewayPortAllocationTest.java @@ -6,14 +6,11 @@ import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.ApplicationContainer; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg; import org.junit.Test; -import org.xml.sax.SAXException; -import java.io.IOException; import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * Tests that qrserver is assigned port Defaults.getDefaults().vespaWebServicePort() even if there is a HTTP gateway configured earlier in @@ -24,7 +21,7 @@ import static org.junit.Assert.assertTrue; public class QrserverAndGatewayPortAllocationTest { @Test - public void testPorts() throws IOException, SAXException { + public void testPorts() { String appDir = "src/test/cfg/application/app_qrserverandgw/"; VespaModelCreatorWithFilePkg creator = new VespaModelCreatorWithFilePkg(appDir); VespaModel vespaModel = creator.create(); diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/HostsXmlProvisionerTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/HostsXmlProvisionerTest.java index 19d7def5787..1c7925b935a 100644 --- a/config-model/src/test/java/com/yahoo/config/model/provision/HostsXmlProvisionerTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/provision/HostsXmlProvisionerTest.java @@ -127,7 +127,6 @@ public class HostsXmlProvisionerTest { @Test public void require_singlenode_HostAlias_is_used_if_hosts_xml() { - String servicesXml = "<container id='default' version='1.0' />"; HostsXmlProvisioner hostProvisioner = createProvisioner(oneHost); HostSpec hostSpec = hostProvisioner.allocateHost(Container.SINGLENODE_CONTAINER_SERVICESPEC); assertThat(hostSpec.hostname(), is("test1.yahoo.com")); diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java index dc0312aef8e..c7ee5188cd1 100644 --- a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java +++ b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java @@ -14,7 +14,7 @@ import com.yahoo.config.provision.Zone; import com.yahoo.config.provisioning.FlavorsConfig; import com.yahoo.container.core.ApplicationMetadataConfig; import com.yahoo.search.config.QrStartConfig; -import com.yahoo.searchdefinition.parser.ParseException; +import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.HostSystem; @@ -609,7 +609,7 @@ public class ModelProvisioningTest { } @Test - public void testClusterControllersCanSupplementWithAllContainerClusters() throws ParseException { + public void testClusterControllersCanSupplementWithAllContainerClusters() { String services = "<?xml version='1.0' encoding='utf-8' ?>\n" + "<services>" + @@ -992,6 +992,10 @@ public class ModelProvisioningTest { " <admin version='3.0'>" + " <nodes count='3'/>" + // Ignored " </admin>" + + " <container version='1.0' id='container'>" + + " <search/>" + + " <nodes count='2'/>" + + " </container>" + " <content version='1.0' id='bar'>" + " <redundancy reply-after='8'>12</redundancy>" + " <documents>" + @@ -1003,7 +1007,7 @@ public class ModelProvisioningTest { " </content>" + "</services>"; - int numberOfHosts = 4; + int numberOfHosts = 6; VespaModelTester tester = new VespaModelTester(); tester.addHosts(numberOfHosts); VespaModel model = tester.createModel(services, false); @@ -1014,6 +1018,7 @@ public class ModelProvisioningTest { assertEquals(4, cluster.redundancy().effectiveFinalRedundancy()); assertEquals(4, cluster.redundancy().effectiveReadyCopies()); assertEquals(4, cluster.getSearch().getIndexed().getDispatchSpec().getGroups().size()); + assertEquals(4, cluster.getSearch().getIndexed().getSearchableCopies()); assertFalse(cluster.getRootGroup().getPartitions().isPresent()); assertEquals(4, cluster.getRootGroup().getNodes().size()); assertEquals(0, cluster.getRootGroup().getSubgroups().size()); @@ -1026,10 +1031,14 @@ public class ModelProvisioningTest { assertThat(cluster.getRootGroup().getNodes().get(2).getConfigId(), is("bar/storage/2")); assertThat(cluster.getRootGroup().getNodes().get(3).getDistributionKey(), is(3)); assertThat(cluster.getRootGroup().getNodes().get(3).getConfigId(), is("bar/storage/3")); + PartitionsConfig.Builder partBuilder = new PartitionsConfig.Builder(); + cluster.getSearch().getIndexed().getTLDs().get(0).getConfig(partBuilder); + PartitionsConfig partCFg = partBuilder.build(); + assertEquals(4, partCFg.dataset(0).searchablecopies()); } @Test - public void testUsingNodesAndGroupCountAttributesAndGettingJustOneNode() throws ParseException { + public void testUsingNodesAndGroupCountAttributesAndGettingJustOneNode() { String services = "<?xml version='1.0' encoding='utf-8' ?>\n" + "<services>" + @@ -1069,7 +1078,7 @@ public class ModelProvisioningTest { } @Test(expected = IllegalArgumentException.class) - public void testRequiringMoreNodesThanAreAvailable() throws ParseException { + public void testRequiringMoreNodesThanAreAvailable() { String services = "<?xml version='1.0' encoding='utf-8' ?>\n" + "<services>" + diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/FieldOfTypeDocumentTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/FieldOfTypeDocumentTestCase.java index 9942b563297..be3bae05c5b 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/FieldOfTypeDocumentTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/FieldOfTypeDocumentTestCase.java @@ -1,10 +1,13 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition; -import com.yahoo.document.*; +import com.yahoo.document.DataType; +import com.yahoo.document.DocumentType; +import com.yahoo.document.DocumentTypeManager; +import com.yahoo.document.DocumentTypeManagerConfigurer; +import com.yahoo.document.Field; import com.yahoo.document.config.DocumentmanagerConfig; import com.yahoo.searchdefinition.derived.Deriver; -import com.yahoo.searchdefinition.parser.ParseException; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; @@ -19,7 +22,7 @@ import static org.junit.Assert.assertSame; public class FieldOfTypeDocumentTestCase extends SearchDefinitionTestCase { @Test - public void testDocument() throws IOException, ParseException { + public void testDocument() throws IOException { List<String> sds = new ArrayList<>(); sds.add("src/test/examples/music.sd"); diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectRankingExpressionFileRefTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectRankingExpressionFileRefTestCase.java index 5b8b05e51da..16c6b070551 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectRankingExpressionFileRefTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectRankingExpressionFileRefTestCase.java @@ -10,7 +10,6 @@ import org.junit.Test; import java.io.IOException; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectSummaryTypesTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectSummaryTypesTestCase.java index 627394bb6ea..c145c0e5634 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectSummaryTypesTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/IncorrectSummaryTypesTestCase.java @@ -4,8 +4,6 @@ package com.yahoo.searchdefinition; import com.yahoo.searchdefinition.parser.ParseException; import org.junit.Test; -import java.io.IOException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** @@ -15,7 +13,7 @@ import static org.junit.Assert.fail; */ public class IncorrectSummaryTypesTestCase extends SearchDefinitionTestCase { @Test - public void testImportingIncorrect() throws IOException, ParseException { + public void testImportingIncorrect() throws ParseException { try { SearchBuilder.createFromString( "search incorrectsummarytypes {\n" + diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/NameFieldCheckTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/NameFieldCheckTestCase.java index b539c65150d..d2360453976 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/NameFieldCheckTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/NameFieldCheckTestCase.java @@ -1,11 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition; -import com.yahoo.searchdefinition.parser.ParseException; import org.junit.Test; -import java.io.IOException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -20,7 +17,7 @@ import static org.junit.Assert.fail; public class NameFieldCheckTestCase extends SearchDefinitionTestCase { @Test - public void testNameField() throws IOException, ParseException { + public void testNameField() { try { SearchBuilder.createFromString( "search simple {\n" + diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/OutsideTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/OutsideTestCase.java index ed4760e2432..5ac37bf0a3a 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/OutsideTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/OutsideTestCase.java @@ -2,7 +2,6 @@ package com.yahoo.searchdefinition; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Ignore; import org.junit.Test; import java.io.IOException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionConstantsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionConstantsTestCase.java index aa4515e4044..51508414205 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionConstantsTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionConstantsTestCase.java @@ -13,7 +13,6 @@ import org.junit.Test; import java.util.List; import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; /** * @author bratseth diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionInliningTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionInliningTestCase.java index 55f0fdd19ac..c0a90b6aaa1 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionInliningTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionInliningTestCase.java @@ -13,7 +13,6 @@ import java.util.Optional; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * @author bratseth diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionValidationTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionValidationTestCase.java index 57d897498a5..0107331fe68 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionValidationTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankingExpressionValidationTestCase.java @@ -8,7 +8,6 @@ import ai.vespa.rankingexpression.importer.configmodelview.ImportedMlModels; import com.yahoo.yolean.Exceptions; import org.junit.Test; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionTestCase.java index 21c7362f793..a6c6939f830 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionTestCase.java @@ -5,12 +5,10 @@ import com.yahoo.io.IOUtils; import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.File; import java.io.IOException; import static helpers.CompareConfigTestHelper.assertSerializedConfigEquals; import static helpers.CompareConfigTestHelper.assertSerializedConfigFileEquals; -import static org.junit.Assert.assertEquals; public abstract class SearchDefinitionTestCase { diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionsParsingTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionsParsingTestCase.java index a26154fc8da..fda15528eda 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionsParsingTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/SearchDefinitionsParsingTestCase.java @@ -2,14 +2,9 @@ package com.yahoo.searchdefinition; import java.io.IOException; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/StructTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/StructTestCase.java index 3d98ce46de7..001ad64e2da 100755 --- a/config-model/src/test/java/com/yahoo/searchdefinition/StructTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/StructTestCase.java @@ -17,7 +17,7 @@ import static org.junit.Assert.fail; public class StructTestCase extends SearchDefinitionTestCase { @Test - public void testStruct() throws IOException, ParseException { + public void testStruct() throws IOException { assertConfigFile("src/test/examples/structresult.cfg", new DocumentmanagerConfig(Deriver.getDocumentManagerConfig("src/test/examples/struct.sd")).toString() + "\n"); } @@ -33,7 +33,7 @@ public class StructTestCase extends SearchDefinitionTestCase { } @Test - public void testStructAndDocumentWithSameNames() throws IOException, ParseException { + public void testStructAndDocumentWithSameNames() { try { DocumenttypesConfig.Builder dt = Deriver.getDocumentTypesConfig("src/test/examples/structanddocumentwithsamenames.sd"); } catch (Exception e) { diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/SummaryTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/SummaryTestCase.java index 7e6eaa0683a..f94ac1c285c 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/SummaryTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/SummaryTestCase.java @@ -1,11 +1,9 @@ package com.yahoo.searchdefinition; -import com.yahoo.config.application.api.DeployLogger; import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.model.test.utils.DeployLoggerStub; import org.junit.Test; -import java.io.IOException; import java.util.logging.Level; import static org.junit.Assert.assertEquals; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/AbstractExportingTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/AbstractExportingTestCase.java index 7fbca88cb61..78382ccdad6 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/AbstractExportingTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/AbstractExportingTestCase.java @@ -3,7 +3,6 @@ package com.yahoo.searchdefinition.derived; import com.yahoo.document.DocumenttypesConfig; import com.yahoo.document.config.DocumentmanagerConfig; -import com.yahoo.search.query.profile.QueryProfileRegistry; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.SearchDefinitionTestCase; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/DeriverTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/DeriverTestCase.java index 47915580017..cc27a0d6067 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/DeriverTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/DeriverTestCase.java @@ -4,19 +4,12 @@ package com.yahoo.searchdefinition.derived; import com.yahoo.document.DataType; import com.yahoo.document.DocumentTypeManager; import com.yahoo.document.config.DocumentmanagerConfig; -import com.yahoo.io.IOUtils; -import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.SearchDefinitionTestCase; -import com.yahoo.searchdefinition.document.SDDocumentType; import org.junit.Test; -import java.io.File; -import java.io.IOException; + import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; /** * Tests deriving using the Deriver facade diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java index 4600f6ae4c6..ebd2c752d5e 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java @@ -2,7 +2,6 @@ package com.yahoo.searchdefinition.derived; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Ignore; import org.junit.Test; import java.io.IOException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java index 8ad6abbbb42..9b363bc5734 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/InheritanceTestCase.java @@ -16,7 +16,6 @@ import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import org.junit.rules.TemporaryFolder; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/StreamingStructTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/StreamingStructTestCase.java index 1be9ee3f465..592fd6c45ed 100755 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/StreamingStructTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/StreamingStructTestCase.java @@ -4,9 +4,7 @@ package com.yahoo.searchdefinition.derived; import com.yahoo.searchdefinition.parser.ParseException; import org.junit.Test; -import java.io.File; import java.io.IOException; -import java.util.Arrays; /** * Tests VSM configuration deriving for structs diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java index 17c767f4029..b03db1d7f2e 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/SummaryMapTestCase.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.Iterator; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Tests summary map extraction @@ -27,7 +28,7 @@ public class SummaryMapTestCase extends SearchDefinitionTestCase { @Test public void testDeriving() throws IOException, ParseException { Search search = SearchBuilder.buildFromFile("src/test/examples/simple.sd"); - SummaryMap summaryMap=new SummaryMap(search, new Summaries(search, new BaseDeployLogger())); + SummaryMap summaryMap=new SummaryMap(search); Iterator transforms=summaryMap.resultTransformIterator(); FieldResultTransform transform = (FieldResultTransform)transforms.next(); @@ -66,7 +67,7 @@ public class SummaryMapTestCase extends SearchDefinitionTestCase { assertEquals("access", transform.getFieldName()); assertEquals(SummaryTransform.ATTRIBUTE,transform.getTransform()); - assertTrue(!transforms.hasNext()); + assertFalse(transforms.hasNext()); } @Test public void testPositionDeriving() { @@ -77,7 +78,7 @@ public class SummaryMapTestCase extends SearchDefinitionTestCase { SDField field = document.addField(fieldName, PositionDataType.INSTANCE); field.parseIndexingScript("{ attribute | summary }"); new Processing().process(search, new BaseDeployLogger(), new RankProfileRegistry(), new QueryProfiles(), true, false); - SummaryMap summaryMap = new SummaryMap(search, new Summaries(search, new BaseDeployLogger())); + SummaryMap summaryMap = new SummaryMap(search); Iterator transforms = summaryMap.resultTransformIterator(); @@ -106,7 +107,7 @@ public class SummaryMapTestCase extends SearchDefinitionTestCase { assertEquals("location_zcurve", transform.getFieldName()); assertEquals(SummaryTransform.ATTRIBUTE,transform.getTransform()); - assertTrue(!transforms.hasNext()); + assertFalse(transforms.hasNext()); SummarymapConfig.Builder scb = new SummarymapConfig.Builder(); summaryMap.getConfig(scb); @@ -143,7 +144,7 @@ public class SummaryMapTestCase extends SearchDefinitionTestCase { @Test public void testFailOnSummaryFieldSourceCollision() { try { - Search search = SearchBuilder.buildFromFile("src/test/examples/summaryfieldcollision.sd"); + SearchBuilder.buildFromFile("src/test/examples/summaryfieldcollision.sd"); } catch (Exception e) { assertTrue(e.getMessage().matches(".*equally named field.*")); } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/TwoStreamingStructsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/TwoStreamingStructsTestCase.java index 179aced3540..a70877a4f4f 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/TwoStreamingStructsTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/TwoStreamingStructsTestCase.java @@ -3,7 +3,6 @@ package com.yahoo.searchdefinition.derived; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Ignore; import org.junit.Test; import java.io.IOException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFieldsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFieldsTestCase.java index a385ed09809..e230840bcaa 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFieldsTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/AdjustPositionSummaryFieldsTestCase.java @@ -1,20 +1,9 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.processing; -import com.google.common.collect.ImmutableMap; -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.document.DataType; import com.yahoo.document.PositionDataType; -import com.yahoo.document.ReferenceDataType; -import com.yahoo.document.TemporaryStructuredDataType; -import com.yahoo.searchdefinition.DocumentReference; -import com.yahoo.searchdefinition.DocumentReferences; import com.yahoo.searchdefinition.Search; -import com.yahoo.searchdefinition.document.SDDocumentType; -import com.yahoo.searchdefinition.document.SDField; -import com.yahoo.searchdefinition.document.TemporaryImportedField; -import com.yahoo.searchdefinition.document.TemporarySDField; import com.yahoo.vespa.documentmodel.DocumentSummary; import com.yahoo.vespa.documentmodel.SummaryField; import com.yahoo.vespa.documentmodel.SummaryTransform; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java index 3e3cd932e55..c679bb7e61f 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ImportedFieldsResolverTestCase.java @@ -1,27 +1,16 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.processing; -import com.google.common.collect.ImmutableMap; -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.document.DataType; -import com.yahoo.document.ReferenceDataType; -import com.yahoo.document.TemporaryStructuredDataType; import com.yahoo.document.TensorDataType; -import com.yahoo.searchdefinition.DocumentReference; -import com.yahoo.searchdefinition.DocumentReferences; import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.document.ImmutableImportedSDField; import com.yahoo.searchdefinition.document.ImmutableSDField; import com.yahoo.searchdefinition.document.ImportedField; import com.yahoo.searchdefinition.document.ImportedFields; -import com.yahoo.searchdefinition.document.SDDocumentType; import com.yahoo.searchdefinition.document.SDField; -import com.yahoo.searchdefinition.document.TemporaryImportedField; import com.yahoo.searchdefinition.document.TemporarySDField; import com.yahoo.tensor.TensorType; -import com.yahoo.vespa.documentmodel.DocumentSummary; -import com.yahoo.vespa.documentmodel.SummaryField; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValidationTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValidationTestCase.java index ad801ed50ab..3eeac1ee710 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValidationTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValidationTestCase.java @@ -4,7 +4,6 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.derived.AbstractExportingTestCase; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Ignore; import org.junit.Test; import java.io.IOException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValuesTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValuesTestCase.java index 8d3f1ba0020..a1c454da822 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValuesTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/IndexingValuesTestCase.java @@ -2,7 +2,6 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Ignore; import org.junit.Test; import java.io.IOException; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/PositionTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/PositionTestCase.java index 9cf555e2c9a..e5fadea6dd8 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/PositionTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/PositionTestCase.java @@ -7,14 +7,11 @@ import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.document.Attribute; import com.yahoo.searchdefinition.document.FieldSet; -import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.documentmodel.SummaryField; import com.yahoo.vespa.documentmodel.SummaryTransform; -import org.junit.Ignore; import org.junit.Test; -import java.io.IOException; import java.util.Arrays; import java.util.Iterator; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java index cba931e81f0..38515c36690 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java @@ -319,7 +319,7 @@ public class RankingExpressionWithTensorFlowTestCase { @Test public void testFunctionGeneration() { final String name = "mnist_saved"; - final String expression = "join(join(reduce(join(join(join(imported_ml_function_" + name + "_dnn_hidden2_add, reduce(constant(" + name + "_dnn_hidden2_Const), sum, d2), f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_dnn_outputs_bias_read), f(a,b)(a + b)), tensor(d0[1])(1.0), f(a,b)(a * b))"; + final String expression = "join(join(reduce(join(join(join(imported_ml_function_" + name + "_dnn_hidden2_add, reduce(rename(constant(" + name + "_dnn_hidden2_Const), d0, d2), sum, d2), f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_dnn_outputs_bias_read), f(a,b)(a + b)), tensor(d0[1])(1.0), f(a,b)(a * b))"; final String functionExpression1 = "join(reduce(join(reduce(rename(input, (d0, d1), (d0, d4)), sum, d0), constant(" + name + "_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(" + name + "_dnn_hidden1_bias_read), f(a,b)(a + b))"; final String functionExpression2 = "join(reduce(join(join(join(imported_ml_function_" + name + "_dnn_hidden1_add, 0.009999999776482582, f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden1_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_hidden2_weights_read), f(a,b)(a * b)), sum, d3), constant(" + name + "_dnn_hidden2_bias_read), f(a,b)(a + b))"; @@ -349,7 +349,7 @@ public class RankingExpressionWithTensorFlowTestCase { " rank-profile my_profile_child inherits my_profile {\n" + " }"; - final String expression = "join(join(reduce(join(join(join(imported_ml_function_" + name + "_dnn_hidden2_add, reduce(constant(" + name + "_dnn_hidden2_Const), sum, d2), f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_dnn_outputs_bias_read), f(a,b)(a + b)), tensor(d0[1])(1.0), f(a,b)(a * b))"; + final String expression = "join(join(reduce(join(join(join(imported_ml_function_" + name + "_dnn_hidden2_add, reduce(rename(constant(" + name + "_dnn_hidden2_Const), d0, d2), sum, d2), f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_dnn_outputs_bias_read), f(a,b)(a + b)), tensor(d0[1])(1.0), f(a,b)(a * b))"; final String functionExpression1 = "join(reduce(join(reduce(rename(input, (d0, d1), (d0, d4)), sum, d0), constant(" + name + "_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(" + name + "_dnn_hidden1_bias_read), f(a,b)(a + b))"; final String functionExpression2 = "join(reduce(join(join(join(imported_ml_function_" + name + "_dnn_hidden1_add, 0.009999999776482582, f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden1_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_hidden2_weights_read), f(a,b)(a * b)), sum, d3), constant(" + name + "_dnn_hidden2_bias_read), f(a,b)(a + b))"; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java index b39c48b67bf..eecab3c03d7 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/ReservedRankingExpressionFunctionNamesTestCase.java @@ -3,7 +3,6 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.searchdefinition.RankProfileRegistry; -import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.parser.ParseException; import org.junit.Test; diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java index 15c1d24ce33..f90320ad686 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java @@ -23,7 +23,6 @@ import org.junit.Test; import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class TensorTransformTestCase extends SearchDefinitionTestCase { diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/WeightedSetSummaryToTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/WeightedSetSummaryToTestCase.java index bee353692b8..ef6bc57223d 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/WeightedSetSummaryToTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/WeightedSetSummaryToTestCase.java @@ -5,12 +5,10 @@ import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.SearchDefinitionTestCase; import com.yahoo.searchdefinition.parser.ParseException; -import com.yahoo.vespa.documentmodel.DocumentSummary; import org.junit.Test; import java.io.IOException; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** @author bratseth */ diff --git a/config-model/src/test/java/com/yahoo/vespa/model/HostPortsTest.java b/config-model/src/test/java/com/yahoo/vespa/model/HostPortsTest.java index e2834291c0d..1ded447993c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/HostPortsTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/HostPortsTest.java @@ -11,12 +11,8 @@ import java.util.ArrayList; import java.util.List; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * @author arnej diff --git a/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java b/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java index 3e9bb6d0615..02b77bdb375 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/HostResourceTest.java @@ -14,7 +14,6 @@ import static com.yahoo.config.provision.ClusterSpec.Type.admin; import static com.yahoo.config.provision.ClusterSpec.Type.container; import static com.yahoo.config.provision.ClusterSpec.Type.content; import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/InstanceResolverTest.java b/config-model/src/test/java/com/yahoo/vespa/model/InstanceResolverTest.java index 0f58c5e3a2a..c96e28035e0 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/InstanceResolverTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/InstanceResolverTest.java @@ -56,7 +56,6 @@ public class InstanceResolverTest { /** * Values unset on builder, trying to set them from def file, but type mismatches there - * @throws Exception */ @Test public void testApplyDefToBuilderMismatches() throws Exception { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java index 1c640a6e4d7..c88d91a3ede 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java @@ -95,7 +95,7 @@ public class ClusterControllerTestCase extends DomBuilderTest { @Test(expected = IllegalArgumentException.class) - public void testSeparateHostsRequired() throws Exception { + public void testSeparateHostsRequired() { String xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<services>\n" + "\n" + @@ -122,7 +122,7 @@ public class ClusterControllerTestCase extends DomBuilderTest { } @Test(expected = IllegalArgumentException.class) - public void testSeparateHostsFromConfigServerRequired() throws Exception { + public void testSeparateHostsFromConfigServerRequired() { String xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<services>\n" + "\n" + @@ -150,7 +150,7 @@ public class ClusterControllerTestCase extends DomBuilderTest { } @Test - public void testStandaloneZooKeeper() throws Exception { + public void testStandaloneZooKeeper() { String xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<services>\n" + "\n" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java index ac76783c2af..be7fc19a429 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java @@ -5,7 +5,6 @@ import com.yahoo.cloud.config.LogforwarderConfig; import com.yahoo.cloud.config.SentinelConfig; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.NullConfigModelRegistry; -import com.yahoo.config.model.api.container.ContainerServiceType; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.config.model.provision.Hosts; @@ -27,7 +26,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; -import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.METRICS_PROXY_CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.QRSERVER; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java index ad6a7de935b..5fe62e6bd1b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java @@ -9,8 +9,6 @@ import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensionsConfig; import ai.vespa.metricsproxy.metric.dimensions.NodeDimensionsConfig; import ai.vespa.metricsproxy.rpc.RpcConnectorConfig; import ai.vespa.metricsproxy.service.VespaServicesConfig; -import com.yahoo.config.provision.Flavor; -import com.yahoo.config.provisioning.FlavorsConfig; import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.admin.monitoring.Metric; @@ -20,7 +18,6 @@ import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.T import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.TestMode.self_hosted; import static com.yahoo.vespa.model.admin.monitoring.DefaultPublicConsumer.DEFAULT_PUBLIC_CONSUMER_ID; import static com.yahoo.vespa.model.admin.monitoring.VespaMetricsConsumer.VESPA_CONSUMER_ID; -import static org.junit.Assert.assertEquals; /** * @author gjoranv @@ -96,9 +93,4 @@ class MetricsProxyModelTester { return new RpcConnectorConfig((RpcConnectorConfig.Builder) model.getConfig(new RpcConnectorConfig.Builder(), CONTAINER_CONFIG_ID)); } - private static Flavor flavorFromString(String name) { - return new Flavor(new FlavorsConfig.Flavor(new FlavorsConfig.Flavor.Builder(). - name(name))); - } - } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidatorTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidatorTestCase.java index a0c05193661..3ba3745f46e 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidatorTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ComplexAttributeFieldsValidatorTestCase.java @@ -49,36 +49,6 @@ public class ComplexAttributeFieldsValidatorTestCase { } @Test - public void throws_when_attribute_is_set_on_a_field_with_struct_sub_type() throws IOException, SAXException { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage("For field 'struct_array.f2': Setting attribute on a field that has struct or map sub-type(s) is not supported"); - createModelAndValidate(joinLines(createSearchDefintionWithInvalidStructFieldAttribute("array<s1>"))); - } - - @Test - public void throws_when_attribute_is_set_on_a_field_with_map_sub_type() throws IOException, SAXException { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage("For field 'struct_array.f2': Setting attribute on a field that has struct or map sub-type(s) is not supported"); - createModelAndValidate(joinLines(createSearchDefintionWithInvalidStructFieldAttribute("map<string, int>"))); - } - - private String createSearchDefintionWithInvalidStructFieldAttribute(String invalidFieldType) { - return joinLines("search test {", - " document test {", - " struct s1 {", - " field f1 type int {}", - " }", - " struct s2 {", - " field f2 type " + invalidFieldType + " {}", - " }", - " field struct_array type array<s2> {", - " struct-field f2 { indexing: attribute }", - " }", - " }", - "}"); - } - - @Test public void validation_passes_when_only_supported_struct_field_attributes_are_used() throws IOException, SAXException { createModelAndValidate(joinLines("search test {", " document test {", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java index bce28dd9236..895aa4f6a36 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java @@ -3,22 +3,13 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; -import com.yahoo.config.model.api.ConfigChangeAction; -import com.yahoo.config.model.api.ConfigChangeRefeedAction; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.ValidationTester; -import com.yahoo.vespa.model.search.AbstractSearchCluster; import com.yahoo.yolean.Exceptions; import org.junit.Test; -import org.xml.sax.SAXException; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -27,7 +18,7 @@ import static org.junit.Assert.fail; public class ClusterSizeReductionValidatorTest { @Test - public void testSizeReductionValidation() throws IOException, SAXException { + public void testSizeReductionValidation() { ValidationTester tester = new ValidationTester(30); VespaModel previous = tester.deploy(null, getServices(30), Environment.prod, null).getFirst(); @@ -43,7 +34,7 @@ public class ClusterSizeReductionValidatorTest { } @Test - public void testSizeReductionValidationMinimalDecreaseIsAllowed() throws IOException, SAXException { + public void testSizeReductionValidationMinimalDecreaseIsAllowed() { ValidationTester tester = new ValidationTester(30); VespaModel previous = tester.deploy(null, getServices(3), Environment.prod, null).getFirst(); @@ -61,7 +52,7 @@ public class ClusterSizeReductionValidatorTest { */ @Test - public void testOverridingSizereductionValidation() throws IOException, SAXException { + public void testOverridingSizereductionValidation() { ValidationTester tester = new ValidationTester(30); VespaModel previous = tester.deploy(null, getServices(30), Environment.prod, null).getFirst(); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java index eeeb5344e0b..9318130bb4f 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java @@ -22,7 +22,6 @@ import org.junit.Test; import java.time.Instant; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/GlobalDocumentChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/GlobalDocumentChangeValidatorTest.java index 68b21222fc5..465b1a5d3ba 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/GlobalDocumentChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/GlobalDocumentChangeValidatorTest.java @@ -1,17 +1,10 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.application.validation.change; -import com.yahoo.config.model.api.ConfigChangeAction; -import com.yahoo.config.model.api.ConfigChangeRefeedAction; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.ValidationTester; import org.junit.Test; -import org.xml.sax.SAXException; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -23,7 +16,7 @@ import static org.junit.Assert.assertFalse; public class GlobalDocumentChangeValidatorTest { @Test - public void testChangGlobalAttribute() throws IOException, SAXException { + public void testChangGlobalAttribute() { testChangeGlobalAttribute(true, false, false, null); testChangeGlobalAttribute(true, true, true, null); testChangeGlobalAttribute(false, false, true, null); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java index cca112f3bd2..ab56178bee3 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java @@ -8,9 +8,7 @@ import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.ValidationTester; import com.yahoo.vespa.model.search.AbstractSearchCluster; import org.junit.Test; -import org.xml.sax.SAXException; -import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -23,7 +21,7 @@ import static org.junit.Assert.assertTrue; public class IndexingModeChangeValidatorTest { @Test - public void testChangingIndexMode() throws IOException, SAXException { + public void testChangingIndexMode() { ValidationTester tester = new ValidationTester(); VespaModel oldModel = diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java index c8fe3c46c9a..038fe2c8675 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java @@ -10,7 +10,6 @@ import java.util.List; import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newRefeedAction; import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newRestartAction; -import static org.junit.Assert.assertEquals; public class AttributeChangeValidatorTest { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java index aeddd05209f..4064e53dfb7 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java @@ -180,7 +180,7 @@ public class DocumentTypeChangeValidatorTest { } @Test - public void requireThatChangingTargetTypeOfReferenceFieldIsNotOK() throws Exception { + public void requireThatChangingTargetTypeOfReferenceFieldIsNotOK() { DocumentTypeChangeValidator validator = new DocumentTypeChangeValidator( createDocumentTypeWithReferenceField("oldDoc"), createDocumentTypeWithReferenceField("newDoc")); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/UserConfigBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/UserConfigBuilderTest.java index d1ef1010b73..6bfd55ca5de 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/UserConfigBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/UserConfigBuilderTest.java @@ -8,7 +8,6 @@ import com.yahoo.config.model.deploy.ConfigDefinitionStore; import com.yahoo.test.SimpletypesConfig; import com.yahoo.config.model.producer.UserConfigRepo; import com.yahoo.config.model.builder.xml.XmlHelper; -import com.yahoo.vespa.config.ConfigDefinition; import com.yahoo.vespa.config.ConfigDefinitionKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.ConfigPayloadBuilder; @@ -17,10 +16,7 @@ import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Optional; @@ -30,47 +26,42 @@ import static org.junit.Assert.*; /** * @author Ulf Lilleengen - * @since 5.1 */ public class UserConfigBuilderTest { - private final ConfigDefinitionStore configDefinitionStore = new ConfigDefinitionStore() { - @Override - public Optional<ConfigDefinition> getConfigDefinition(ConfigDefinitionKey defKey) { return Optional.empty(); } - }; + private final ConfigDefinitionStore configDefinitionStore = defKey -> Optional.empty(); @Test - public void require_that_simple_config_is_resolved() throws ParserConfigurationException, IOException, SAXException { - Element configRoot = getDocument("<config name=\"simpletypes\">" + + public void require_that_simple_config_is_resolved() { + Element configRoot = getDocument("<config name=\"test.simpletypes\">" + " <intval>13</intval>" + "</config>" + - "<config name=\"simpletypes\" version=\"1\">" + + "<config name=\"test.simpletypes\" version=\"1\">" + " <stringval>foolio</stringval>" + "</config>"); UserConfigRepo map = UserConfigBuilder.build(configRoot, configDefinitionStore, new BaseDeployLogger()); assertFalse(map.isEmpty()); - ConfigDefinitionKey key = new ConfigDefinitionKey("simpletypes", "config"); + ConfigDefinitionKey key = new ConfigDefinitionKey("simpletypes", "test"); assertNotNull(map.get(key)); SimpletypesConfig config = createConfig(SimpletypesConfig.class, map.get(key)); assertThat(config.intval(), is(13)); assertThat(config.stringval(), is("foolio")); } - public static <ConfigType extends ConfigInstance> ConfigType createConfig(Class<ConfigType> clazz, ConfigPayloadBuilder builder) { + private static <ConfigType extends ConfigInstance> ConfigType createConfig(Class<ConfigType> clazz, ConfigPayloadBuilder builder) { return ConfigPayload.fromBuilder(builder).toInstance(clazz, ""); } - @Test - public void require_that_arrays_config_is_resolved() throws ParserConfigurationException, IOException, SAXException { - Element configRoot = getDocument("<config name=\"arraytypes\">" + + public void require_that_arrays_config_is_resolved() { + Element configRoot = getDocument("<config name=\"test.arraytypes\">" + " <intarr operation=\"append\">13</intarr>" + " <intarr operation=\"append\">10</intarr>" + " <intarr operation=\"append\">1337</intarr>" + "</config>"); UserConfigRepo map = UserConfigBuilder.build(configRoot, configDefinitionStore, new BaseDeployLogger()); assertFalse(map.isEmpty()); - ConfigDefinitionKey key = new ConfigDefinitionKey("arraytypes", "config"); + ConfigDefinitionKey key = new ConfigDefinitionKey("arraytypes", "test"); assertNotNull(map.get(key)); ArraytypesConfig config = createConfig(ArraytypesConfig.class, map.get(key)); assertThat(config.intarr().size(), is(3)); @@ -80,7 +71,7 @@ public class UserConfigBuilderTest { } @Test - public void require_that_arrays_of_structs_are_resolved() throws ParserConfigurationException, IOException, SAXException { + public void require_that_arrays_of_structs_are_resolved() { Element configRoot = getDocument( " <config name='vespa.configdefinition.specialtokens'>" + " <tokenlist operation='append'>" + @@ -107,16 +98,16 @@ public class UserConfigBuilderTest { } @Test - public void no_exception_when_config_class_does_not_exist() throws ParserConfigurationException, IOException, SAXException { - Element configRoot = getDocument("<config name=\"unknown\">" + + public void no_exception_when_config_class_does_not_exist() { + Element configRoot = getDocument("<config name=\"is.unknown\">" + " <foo>1</foo>" + "</config>"); UserConfigRepo repo = UserConfigBuilder.build(configRoot, configDefinitionStore, new BaseDeployLogger()); - ConfigPayloadBuilder builder = repo.get(new ConfigDefinitionKey("unknown", "config")); + ConfigPayloadBuilder builder = repo.get(new ConfigDefinitionKey("unknown", "is")); assertNotNull(builder); } - private Element getDocument(String xml) throws ParserConfigurationException { + private Element getDocument(String xml) { Reader xmlReader = new StringReader("<model>" + xml + "</model>"); Document doc; try { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/Bug6068056Test.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/Bug6068056Test.java index b90c3173bec..6bc161a0212 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/Bug6068056Test.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/Bug6068056Test.java @@ -44,7 +44,7 @@ public class Bug6068056Test { "</services>"; @Test(expected = RuntimeException.class) - public void testContainerClusterCalledDocproc() throws Exception { + public void testContainerClusterCalledDocproc() { VespaModelCreatorWithMockPkg creator = new VespaModelCreatorWithMockPkg(HOSTS, SERVICES); creator.create(); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java index c0dd894695e..567e54ed4c4 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/ContentBuilderTest.java @@ -4,7 +4,6 @@ package com.yahoo.vespa.model.builder.xml.dom; import com.yahoo.collections.CollectionUtil; import com.yahoo.config.ConfigInstance; import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.model.api.container.ContainerServiceType; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilderTest.java index 765d825bc2b..a62255c4c5c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomConfigPayloadBuilderTest.java @@ -16,9 +16,12 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; -import javax.xml.parsers.ParserConfigurationException; -import java.io.*; -import java.util.ArrayList; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.Reader; +import java.io.StringReader; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -33,7 +36,7 @@ import static org.junit.Assert.fail; public class DomConfigPayloadBuilderTest { @Test - public void testFunctionTest_DefaultValues() throws FileNotFoundException, ParserConfigurationException { + public void testFunctionTest_DefaultValues() throws FileNotFoundException { Element configRoot = getDocument(new FileReader(new File("src/test/cfg/admin/userconfigs/functiontest-defaultvalues.xml"))); ConfigPayload config = ConfigPayload.fromBuilder(new DomConfigPayloadBuilder(null).build(configRoot)); String expected = "" @@ -77,9 +80,9 @@ public class DomConfigPayloadBuilderTest { } @Test - public void put_to_leaf_map() throws Exception { + public void put_to_leaf_map() { Reader xmlConfig = new StringReader("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + - "<config name=\"foobar\">" + + "<config name=\"test.foobar\">" + " <intmap>" + " <item key=\"bar\">1338</item>" + " <item key=\"foo\">1337</item>" + @@ -90,9 +93,9 @@ public class DomConfigPayloadBuilderTest { } @Test - public void put_to_inner_map() throws Exception { + public void put_to_inner_map() { Reader xmlConfig = new StringReader("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + - "<config name=\"foobar\">" + + "<config name=\"test.foobar\">" + " <innermap>" + " <item key=\"bar\">" + " <foo>baz</foo>" + @@ -107,9 +110,9 @@ public class DomConfigPayloadBuilderTest { } @Test - public void put_to_nested_map() throws Exception { + public void put_to_nested_map() { Reader xmlConfig = new StringReader("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + - "<config name=\"foobar\">" + + "<config name=\"test.foobar\">" + " <nestedmap>" + " <item key=\"bar\">" + " <inner>" + @@ -132,10 +135,10 @@ public class DomConfigPayloadBuilderTest { } @Test - public void append_to_leaf_array() throws Exception { + public void append_to_leaf_array() { // Simulate user config from vespa-services.xml Reader xmlConfig = new StringReader("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + - "<config name=\"function-test\">" + + "<config name=\"a.function-test\">" + " <intarr operation=\"append\">1</intarr>" + " <intarr operation=\"append\">2</intarr>" + "</config> "); @@ -144,9 +147,9 @@ public class DomConfigPayloadBuilderTest { } @Test - public void camel_case_via_dashes() throws Exception { + public void camel_case_via_dashes() { Reader xmlConfig = new StringReader("<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + - "<config name=\"function-test\">" + + "<config name=\"test.function-test\">" + " <some-struct> <any-value>17</any-value> </some-struct>" + "</config> "); ConfigPayload userConfig = ConfigPayload.fromBuilder(new DomConfigPayloadBuilder(null).build(getDocument(xmlConfig))); @@ -155,7 +158,7 @@ public class DomConfigPayloadBuilderTest { // Verifies that an exception is thrown when the root element is not 'config'. @Test - public void testFailWrongTagName() throws FileNotFoundException, ParserConfigurationException { + public void testFailWrongTagName() { Element configRoot = getDocument(new StringReader("<configs name=\"foo\"/>")); try { new DomConfigPayloadBuilder(null).build(configRoot); @@ -168,7 +171,7 @@ public class DomConfigPayloadBuilderTest { // Verifies that an exception is thrown when the root element is not 'config'. @Test - public void testFailNoNameAttribute() throws FileNotFoundException, ParserConfigurationException { + public void testFailNoNameAttribute() { Element configRoot = getDocument(new StringReader("<config/>")); try { new DomConfigPayloadBuilder(null).build(configRoot); @@ -180,53 +183,17 @@ public class DomConfigPayloadBuilderTest { } @Test - public void testNamespace() throws FileNotFoundException, ParserConfigurationException { - Element configRoot = getDocument(new StringReader("<config name=\"function-test\" namespace=\"config\">" + - "<int_val>1</int_val> +" + - "</config>")); - ConfigPayload config = ConfigPayload.fromBuilder(new DomConfigPayloadBuilder(null).build(configRoot)); - assertPayload("{\"int_val\":\"1\"}", config); - - configRoot = getDocument(new StringReader("<config name=\"config.function-test\">" + - "<int_val>1</int_val> +" + - "</config>")); - config = ConfigPayload.fromBuilder(new DomConfigPayloadBuilder(null).build(configRoot)); - assertPayload("{\"int_val\":\"1\"}", config); - - configRoot = getDocument(new StringReader("<config name=\"config.function_test\">" + - "<int_val>1</int_val> +" + - "</config>")); - config = ConfigPayload.fromBuilder(new DomConfigPayloadBuilder(null).build(configRoot)); - assertPayload("{\"int_val\":\"1\"}", config); - } - - @Test - public void testNameParsing() throws FileNotFoundException, ParserConfigurationException { - Element configRoot = getDocument(new StringReader("<config name=\"function-test\" version=\"1\" namespace=\"config\">" + + public void testNameParsing() { + Element configRoot = getDocument(new StringReader("<config name=\"test.function-test\" version=\"1\">" + "<int_val>1</int_val> +" + "</config>")); ConfigDefinitionKey key = DomConfigPayloadBuilder.parseConfigName(configRoot); assertThat(key.getName(), is("function-test")); - assertThat(key.getNamespace(), is("config")); - - configRoot = getDocument(new StringReader("<config name=\"function_test\" version=\"1\">" + - "<int_val>1</int_val> +" + - "</config>")); - key = DomConfigPayloadBuilder.parseConfigName(configRoot); - assertThat(key.getName(), is("function_test")); - assertThat(key.getNamespace(), is("config")); - - // Both namespace and name in name attribute - configRoot = getDocument(new StringReader("<config name=\"config.function-test\" version=\"1\">" + - "<int_val>1</int_val> +" + - "</config>")); - key = DomConfigPayloadBuilder.parseConfigName(configRoot); - assertThat(key.getName(), is("function-test")); - assertThat(key.getNamespace(), is("config")); + assertThat(key.getNamespace(), is("test")); } @Test(expected = ConfigurationRuntimeException.class) - public void testNameParsingInvalidName() throws FileNotFoundException, ParserConfigurationException { + public void testNameParsingInvalidName() { Element configRoot = getDocument(new StringReader("<config name=\" function-test\" version=\"1\">" + "<int_val>1</int_val> +" + "</config>")); @@ -234,17 +201,17 @@ public class DomConfigPayloadBuilderTest { } @Test(expected = ConfigurationRuntimeException.class) - public void testNameParsingInvalidNamespace() throws FileNotFoundException, ParserConfigurationException { - Element configRoot = getDocument(new StringReader("<config name=\"function-test\" namespace=\"_foo\" version=\"1\">" + + public void testNameParsingInvalidNamespace() { + Element configRoot = getDocument(new StringReader("<config name=\"_foo.function-test\" version=\"1\">" + "<int_val>1</int_val> +" + "</config>")); DomConfigPayloadBuilder.parseConfigName(configRoot); } @Test - public void require_that_item_syntax_works_with_leaf() throws ParserConfigurationException { + public void require_that_item_syntax_works_with_leaf() { Element configRoot = getDocument( - "<config name=\"arraytypes\" version=\"1\">" + + "<config name=\"test.arraytypes\" version=\"1\">" + " <intarr>" + " <item>13</item>" + " <item>10</item>" + @@ -257,9 +224,9 @@ public class DomConfigPayloadBuilderTest { } @Test - public void require_that_item_syntax_works_with_struct() throws ParserConfigurationException { + public void require_that_item_syntax_works_with_struct() { Element configRoot = getDocument( - "<config name=\"arraytypes\" version=\"1\">" + + "<config name=\"test.arraytypes\" version=\"1\">" + " <lolarray>" + " <item><foo>hei</foo><bar>hei2</bar></item>" + " <item><foo>hoo</foo><bar>hoo2</bar></item>" + @@ -273,9 +240,9 @@ public class DomConfigPayloadBuilderTest { } @Test - public void require_that_item_syntax_works_with_struct_array() throws ParserConfigurationException { + public void require_that_item_syntax_works_with_struct_array() { Element configRoot = getDocument( - "<config name=\"arraytypes\" version=\"1\">" + + "<config name=\"test.arraytypes\" version=\"1\">" + " <lolarray>" + " <item><fooarray><item>13</item></fooarray></item>" + " <item><fooarray><item>10</item></fooarray></item>" + @@ -288,18 +255,18 @@ public class DomConfigPayloadBuilderTest { } @Test(expected = ConfigurationRuntimeException.class) - public void require_that_item_is_reserved_in_root() throws ParserConfigurationException { + public void require_that_item_is_reserved_in_root() { Element configRoot = getDocument( - "<config name=\"arraytypes\" version=\"1\">" + + "<config name=\"test.arraytypes\" version=\"1\">" + " <item>13</item>" + "</config>"); new DomConfigPayloadBuilder(null).build(configRoot); } @Test(expected=ConfigurationRuntimeException.class) - public void require_that_exceptions_are_issued() throws ParserConfigurationException, FileNotFoundException { + public void require_that_exceptions_are_issued() throws FileNotFoundException { Element configRoot = getDocument( - "<config name=\"simpletypes\">" + + "<config name=\"test.simpletypes\">" + "<longval>invalid</longval>" + "</config>"); DefParser defParser = new DefParser("simpletypes", @@ -309,7 +276,7 @@ public class DomConfigPayloadBuilderTest { //assertThat(builder.warnings().size(), is(1)); } - private Element getDocument(Reader xmlReader) throws ParserConfigurationException { + private Element getDocument(Reader xmlReader) { Document doc; try { doc = XmlHelper.getDocumentBuilder().parse(new InputSource(xmlReader)); @@ -319,7 +286,7 @@ public class DomConfigPayloadBuilderTest { return doc.getDocumentElement(); } - private Element getDocument(String xml) throws ParserConfigurationException { + private Element getDocument(String xml) { Reader xmlReader = new StringReader(xml); return getDocument(xmlReader); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/LegacyConfigModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/LegacyConfigModelBuilderTest.java index bef2b37d65d..fa4c4d313a4 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/LegacyConfigModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/LegacyConfigModelBuilderTest.java @@ -3,15 +3,14 @@ package com.yahoo.vespa.model.builder.xml.dom; import com.yahoo.config.model.ConfigModel; import com.yahoo.config.model.ConfigModelContext; -import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.builder.xml.ConfigModelId; +import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.model.test.MockRoot; import com.yahoo.text.XML; import org.junit.Test; import org.w3c.dom.Element; -import java.util.Arrays; import java.util.List; import static org.hamcrest.CoreMatchers.is; @@ -19,12 +18,12 @@ import static org.junit.Assert.assertThat; /** * @author Ulf Lilleengen - * @since 5.1 */ public class LegacyConfigModelBuilderTest { + @Test public void testThatProducerIsInserted() { - String services = "<foo><config name=\"bar\"><key>value</key></config></foo>"; + String services = "<foo><config name=\"bar.foo\"><key>value</key></config></foo>"; ModelBuilder builder = new ModelBuilder(); Model model = builder.build(DeployState.createTestState(new MockApplicationPackage.Builder().withServices(services).build()), null, null, new MockRoot(), XML.getDocument(services).getDocumentElement()); @@ -51,7 +50,7 @@ public class LegacyConfigModelBuilderTest { } private static class ModelBuilder extends LegacyConfigModelBuilder<Model> { - public ModelBuilder() { + ModelBuilder() { super(Model.class); } @@ -61,7 +60,7 @@ public class LegacyConfigModelBuilderTest { @Override public List<ConfigModelId> handlesElements() { - return Arrays.asList(ConfigModelId.fromName("foo")); + return List.of(ConfigModelId.fromName("foo")); } } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilderTest.java index dff21904c75..b8d9ad59ae0 100755 --- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilderTest.java @@ -25,7 +25,7 @@ import static org.junit.Assert.assertThat; */ public class VespaDomBuilderTest { - final static String hosts = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + + private final static String hosts = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<hosts>" + " <host name=\"localhost\">" + " <alias>node1</alias>" + @@ -33,9 +33,9 @@ public class VespaDomBuilderTest { " </host>" + "</hosts>"; - final static String services = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + + private final static String services = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<services>" + - " <config name=\"standard\">" + + " <config name=\"a.standard\">" + " <basicStruct>" + " <stringVal>default</stringVal>" + " </basicStruct>" + @@ -45,7 +45,7 @@ public class VespaDomBuilderTest { " <adminserver hostalias=\"node1\" />" + " </admin>" + " <container version=\"1.0\">" + - " <config name=\"standard\">" + + " <config name=\"a.standard\">" + " <basicStruct>" + " <stringVal>qrservers</stringVal>" + " </basicStruct>" + @@ -56,19 +56,7 @@ public class VespaDomBuilderTest { " </container>\n" + "</services>"; - final static String servicesWithNamespace = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + - "<services>" + - " <config name=\"testnamespace\" namespace=\"foo\">" + - " <basicStruct>" + - " <stringVal>default</stringVal>" + - " </basicStruct>" + - " </config> " + - " <admin version=\"2.0\">" + - " <adminserver hostalias=\"node1\" />" + - " </admin>" + - "</services>"; - - final static String servicesWithNamespace2 = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + + private final static String servicesWithNamespace = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<services>" + " <config name=\"foo.testnamespace\">" + " <basicStruct>" + @@ -82,7 +70,7 @@ public class VespaDomBuilderTest { @Test - public void testUserConfigsWithNamespace() throws Exception { + public void testUserConfigsWithNamespace() { VespaModel model = createModel(hosts, servicesWithNamespace); GenericConfig.GenericConfigBuilder builder = @@ -93,16 +81,6 @@ public class VespaDomBuilderTest { " \"stringVal\": \"default\"\n" + " }\n" + "}\n"); - - model = createModel(hosts, servicesWithNamespace2); - - builder = new GenericConfig.GenericConfigBuilder(new ConfigDefinitionKey("testnamespace", "foo"), new ConfigPayloadBuilder()); - model.getConfig(builder, "admin"); - assertEquals(builder.getPayload().toString(), "{\n" + - " \"basicStruct\": {\n" + - " \"stringVal\": \"default\"\n" + - " }\n" + - "}\n"); } @Test diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerIncludeTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerIncludeTest.java index f40981916fa..62a36422dd8 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerIncludeTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerIncludeTest.java @@ -117,7 +117,7 @@ public class ContainerIncludeTest { } @Test - public void included_file_with_xml_schema_violation() throws Exception { + public void included_file_with_xml_schema_violation() { try { VespaModelCreatorWithFilePkg creator = new VespaModelCreatorWithFilePkg("src/test/cfg/container/data/include_xml_error/"); creator.create(true); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java b/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java index ec01716d9c9..faa461e681b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/configserver/TestOptions.java @@ -15,10 +15,6 @@ public class TestOptions implements CloudConfigOptions { private Optional<Integer> rpcPort = Optional.empty(); private Optional<String> environment = Optional.empty(); private Optional<String> region = Optional.empty(); - private Optional<String> defaultFlavor = Optional.empty(); - private Optional<String> defaultAdminFlavor = Optional.empty(); - private Optional<String> defaultContainerFlavor = Optional.empty(); - private Optional<String> defaultContentFlavor = Optional.empty(); private Optional<Boolean> useVespaVersionInRequest = Optional.empty(); private Optional<Boolean> hostedVespa = Optional.empty(); private Optional<Integer> numParallelTenantLoaders = Optional.empty(); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/http/FilterBindingsTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/http/FilterBindingsTest.java index 9056a30eda2..6ec41df6a1f 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/http/FilterBindingsTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/http/FilterBindingsTest.java @@ -11,9 +11,6 @@ import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; import com.yahoo.vespa.model.container.xml.ContainerModelBuilder.Networking; import org.junit.Test; import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -import java.io.IOException; import static com.yahoo.collections.CollectionUtil.first; import static org.hamcrest.MatcherAssert.assertThat; @@ -35,7 +32,7 @@ public class FilterBindingsTest extends DomBuilderTest { } - private void buildContainerCluster(Element containerElem) throws SAXException, IOException { + private void buildContainerCluster(Element containerElem) { ContainerModel model = new ContainerModelBuilder(true, Networking.enable).build(DeployState.createTestState(), null, null, root, containerElem); root.freezeModelTopology(); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/jersey/xml/RestApiTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/jersey/xml/RestApiTest.java index e954e6343de..9ca508019b2 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/jersey/xml/RestApiTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/jersey/xml/RestApiTest.java @@ -104,7 +104,7 @@ public class RestApiTest extends ContainerModelBuilderTestBase { } @Test - public void all_non_restApi_components_are_injected_to_RestApiContext() throws Exception { + public void all_non_restApi_components_are_injected_to_RestApiContext() { ComponentsConfig componentsConfig = root.getConfig(ComponentsConfig.class, CLUSTER_ID); Set<ComponentId> clusterChildrenComponentIds = getContainerCluster(CLUSTER_ID).getAllComponents().stream() diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/MockSearchClusters.java b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/MockSearchClusters.java index 607bc484525..98bc4210602 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/MockSearchClusters.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/MockSearchClusters.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.model.container.search.searchchain; import com.yahoo.vespa.config.search.AttributesConfig; import com.yahoo.vespa.config.search.RankProfilesConfig; -import com.yahoo.config.model.ConfigModelRepo; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig; import com.yahoo.search.config.IndexInfoConfig; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest.java index d81ffedef7f..365a3ea44b5 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest.java @@ -11,9 +11,6 @@ import org.junit.Before; import org.junit.Test; import org.w3c.dom.Element; -import java.util.List; - -import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest2.java b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest2.java index 9dd6f834e62..ef80683abd6 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest2.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTest2.java @@ -20,7 +20,7 @@ public class SearchChainsTest2 { private MockRoot root; @Before - public void prepareTest() throws Exception { + public void prepareTest() { root = new MockRoot("root"); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTestBase.java b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTestBase.java index 51a8333ef6e..8b49d1bff0d 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTestBase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SearchChainsTestBase.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.search.searchchain; -import com.yahoo.binaryprefix.BinaryScaledAmount; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; import com.yahoo.vespa.model.builder.xml.dom.chains.search.DomSearchChainsBuilder; import org.junit.Before; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SourceGroupTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SourceGroupTest.java index d14905ddab0..cc6478fa4a4 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SourceGroupTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/searchchain/SourceGroupTest.java @@ -24,7 +24,7 @@ public class SourceGroupTest { private SearchChains searchChains; @Before - public void setUp() throws Exception { + public void setUp() { root = new MockRoot(); searchChains = new SearchChains(root, "searchchains"); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/QueryProfilesTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/QueryProfilesTestCase.java index 746e771667f..44181234d9a 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/QueryProfilesTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/QueryProfilesTestCase.java @@ -1,26 +1,20 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.search.test; -import com.yahoo.component.ComponentId; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.search.query.profile.QueryProfile; import com.yahoo.search.query.profile.QueryProfileRegistry; -import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry; import com.yahoo.search.query.profile.config.QueryProfileConfigurer; -import com.yahoo.search.query.profile.config.QueryProfileXMLReader; import com.yahoo.search.query.profile.types.FieldDescription; import com.yahoo.search.query.profile.types.FieldType; import com.yahoo.search.query.profile.types.QueryProfileType; import com.yahoo.search.query.profile.types.QueryProfileTypeRegistry; -import com.yahoo.searchdefinition.SearchBuilder; -import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.model.container.search.QueryProfiles; import com.yahoo.vespa.model.test.utils.DeployLoggerStub; import org.junit.Test; import java.io.IOException; import java.util.logging.Level; -import java.util.logging.Logger; import static helpers.CompareConfigTestHelper.assertSerializedConfigFileEquals; import static org.junit.Assert.assertEquals; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/empty.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/empty.cfg index e69de29bb2d..13153de1644 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/empty.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/empty.cfg @@ -0,0 +1 @@ +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/explicit-reference-override.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/explicit-reference-override.cfg index 99e2e3c5dcb..b62e00a2ad5 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/explicit-reference-override.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/explicit-reference-override.cfg @@ -10,4 +10,5 @@ queryprofile[1].property[0].value "a.b" queryprofile[1].property[0].overridable "" queryprofile[1].reference[0].name "a" queryprofile[1].reference[0].value "a1" -queryprofile[1].reference[0].overridable ""
\ No newline at end of file +queryprofile[1].reference[0].overridable "" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsbe-query-profiles-simple.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsbe-query-profiles-simple.cfg index 196b6c3513a..65b4258f174 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsbe-query-profiles-simple.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsbe-query-profiles-simple.cfg @@ -16,4 +16,5 @@ queryprofile[0].queryprofilevariant[0].fordimensionvalues[0] "yahoo" queryprofile[0].queryprofilevariant[0].fordimensionvalues[1] "uk" queryprofile[0].queryprofilevariant[0].fordimensionvalues[2] "sc" queryprofile[0].queryprofilevariant[0].property[0].name "scthumbnail.sourcecountry" -queryprofile[0].queryprofilevariant[0].property[0].value "uk"
\ No newline at end of file +queryprofile[0].queryprofilevariant[0].property[0].value "uk" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsfe-query-profiles-simple.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsfe-query-profiles-simple.cfg index 461f9b606c6..b416abf7c67 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsfe-query-profiles-simple.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/newsfe-query-profiles-simple.cfg @@ -23,4 +23,5 @@ queryprofile[1].id "default" queryprofile[1].type "" queryprofile[1].reference[0].name "source.news" queryprofile[1].reference[0].value "backend/news" -queryprofile[1].reference[0].overridable ""
\ No newline at end of file +queryprofile[1].reference[0].overridable "" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants-configuration.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants-configuration.cfg index c86bba23286..352966d0d8b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants-configuration.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants-configuration.cfg @@ -38,4 +38,5 @@ queryprofile[2].id "wparent2" queryprofile[2].type "" queryprofile[2].property[0].name "a" queryprofile[2].property[0].value "a1" -queryprofile[2].property[0].overridable ""
\ No newline at end of file +queryprofile[2].property[0].overridable "" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants2-configuration.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants2-configuration.cfg index c915cd2efd0..0f644cbb9eb 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants2-configuration.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profile-variants2-configuration.cfg @@ -58,4 +58,5 @@ queryprofile[3].property[1].overridable "false" queryprofile[3].queryprofilevariant[0].fordimensionvalues[0] "love" queryprofile[3].queryprofilevariant[0].fordimensionvalues[1] "default" queryprofile[3].queryprofilevariant[0].property[0].name "defaultIndex" -queryprofile[3].queryprofilevariant[0].property[0].value "default"
\ No newline at end of file +queryprofile[3].queryprofilevariant[0].property[0].value "default" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profiles.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profiles.cfg index 89a971adb15..4a9dabdc6ca 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profiles.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/query-profiles.cfg @@ -102,4 +102,5 @@ queryprofiletype[2].field[0].name "market" queryprofiletype[2].field[0].type "string" queryprofiletype[2].field[0].overridable false queryprofiletype[2].field[0].mandatory false -queryprofiletype[2].field[0].alias ""
\ No newline at end of file +queryprofiletype[2].field[0].alias "" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound-with-reference.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound-with-reference.cfg index e1cca7ed232..92605f504a0 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound-with-reference.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound-with-reference.cfg @@ -23,4 +23,5 @@ queryprofile[2].queryprofilevariant[0].reference[0].name "a" queryprofile[2].queryprofilevariant[0].reference[0].value "a2" queryprofile[2].queryprofilevariant[1].fordimensionvalues[0] "x2" queryprofile[2].queryprofilevariant[1].property[0].name "a.b" -queryprofile[2].queryprofilevariant[1].property[0].value "a.b.x2"
\ No newline at end of file +queryprofile[2].queryprofilevariant[1].property[0].value "a.b.x2" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound.cfg b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound.cfg index d65b3fa5f92..1e7739d9962 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound.cfg +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/search/test/variants-of-explicit-compound.cfg @@ -14,4 +14,5 @@ queryprofile[1].queryprofilevariant[0].property[0].name "a.b" queryprofile[1].queryprofilevariant[0].property[0].value "a.b.x1" queryprofile[1].queryprofilevariant[1].fordimensionvalues[0] "x2" queryprofile[1].queryprofilevariant[1].property[0].name "a.b" -queryprofile[1].queryprofilevariant[1].property[0].value "a.b.x2"
\ No newline at end of file +queryprofile[1].queryprofilevariant[1].property[0].value "a.b.x2" +enableGroupingSessionCache false
\ No newline at end of file diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java index 3fea346f788..37a57fc59c9 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java @@ -13,9 +13,7 @@ import com.yahoo.vespa.model.container.http.xml.HttpBuilder; import com.yahoo.vespa.model.container.jersey.Jersey2Servlet; import org.junit.Test; import org.w3c.dom.Element; -import org.xml.sax.SAXException; -import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -46,7 +44,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { ContainerCluster.ROOT_HANDLER_BINDING); @Test - public void access_control_filter_chain_is_set_up() throws Exception { + public void access_control_filter_chain_is_set_up() { Element clusterElem = DomBuilderTest.parse( " <http>", " <filtering>", @@ -61,7 +59,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void properties_are_set_from_xml() throws Exception { + public void properties_are_set_from_xml() { Element clusterElem = DomBuilderTest.parse( " <http>", " <filtering>", @@ -82,7 +80,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void read_is_disabled_and_write_is_enabled_by_default() throws Exception { + public void read_is_disabled_and_write_is_enabled_by_default() { Element clusterElem = DomBuilderTest.parse( " <http>", " <filtering>", @@ -98,7 +96,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void read_and_write_can_be_overridden() throws Exception { + public void read_and_write_can_be_overridden() { Element clusterElem = DomBuilderTest.parse( " <http>", " <filtering>", @@ -114,7 +112,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void access_control_filter_chain_has_correct_handler_bindings() throws Exception { + public void access_control_filter_chain_has_correct_handler_bindings() { Element clusterElem = DomBuilderTest.parse( "<container version='1.0'>", " <search/>", @@ -145,7 +143,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void handler_can_be_excluded_by_excluding_one_of_its_bindings() throws Exception { + public void handler_can_be_excluded_by_excluding_one_of_its_bindings() { final String notExcludedBinding = "http://*/custom-handler/*"; final String excludedBinding = "http://*/excluded/*"; Element clusterElem = DomBuilderTest.parse( @@ -166,7 +164,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void access_control_filter_chain_has_all_servlet_bindings() throws Exception { + public void access_control_filter_chain_has_all_servlet_bindings() { final String servletPath = "servlet/path"; final String restApiPath = "api/v0"; final Set<String> requiredBindings = ImmutableSet.of(servletPath, restApiPath); @@ -194,7 +192,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void servlet_can_be_excluded_by_excluding_one_of_its_bindings() throws Exception { + public void servlet_can_be_excluded_by_excluding_one_of_its_bindings() { final String servletPath = "servlet/path"; final String notExcludedBinding = "http://*:8081/" + servletPath; final String excludedBinding = "http://*:8080/" + servletPath; @@ -215,7 +213,7 @@ public class AccessControlTest extends ContainerModelBuilderTestBase { } @Test - public void rest_api_can_be_excluded_by_excluding_one_of_its_bindings() throws Exception { + public void rest_api_can_be_excluded_by_excluding_one_of_its_bindings() { final String restApiPath = "api/v0"; final String notExcludedBinding = "http://*:8081/" + restApiPath + Jersey2Servlet.BINDING_SUFFIX;; final String excludedBinding = "http://*:8080/" + restApiPath + Jersey2Servlet.BINDING_SUFFIX;; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java index 3876ce25d96..4ea10c9a1c1 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessLogTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertEquals; public class AccessLogTest extends ContainerModelBuilderTestBase { @Test - public void default_access_log_is_only_added_when_search_is_present() throws Exception { + public void default_access_log_is_only_added_when_search_is_present() { Element cluster1Elem = DomBuilderTest.parse( "<container id='cluster1' version='1.0'>", "<search />", @@ -45,7 +45,7 @@ public class AccessLogTest extends ContainerModelBuilderTestBase { } @Test - public void default_search_access_log_can_be_disabled() throws Exception { + public void default_search_access_log_can_be_disabled() { final String jdiscClusterId = "jdisc-cluster"; Element clusterElem = DomBuilderTest.parse( @@ -69,7 +69,7 @@ public class AccessLogTest extends ContainerModelBuilderTestBase { } @Test - public void access_log_can_be_configured() throws Exception { + public void access_log_can_be_configured() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>", " <accesslog type='vespa' ", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java index 7b382a45730..8b92e1091ca 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java @@ -396,7 +396,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { } @Test - public void nested_components_are_injected_to_handlers() throws Exception { + public void nested_components_are_injected_to_handlers() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>", " <handler id='myHandler'>", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java index 4fec94d4ab4..41997925666 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTestBase.java @@ -71,7 +71,7 @@ public abstract class ContainerModelBuilderTestBase { } @Before - public void prepareTest() throws Exception { + public void prepareTest() { root = new MockRoot("root"); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java index 0695f7b30d7..9794062d83f 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java @@ -10,9 +10,6 @@ import com.yahoo.container.core.identity.IdentityConfig; import com.yahoo.vespa.model.container.IdentityProvider; import org.junit.Test; import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -import java.io.IOException; import static org.junit.Assert.assertEquals; @@ -21,7 +18,7 @@ import static org.junit.Assert.assertEquals; */ public class IdentityBuilderTest extends ContainerModelBuilderTestBase { @Test - public void identity_config_produced_from_deployment_spec() throws IOException, SAXException { + public void identity_config_produced_from_deployment_spec() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'><search /></container>"); String deploymentXml = "<deployment version='1.0' athenz-domain='domain' athenz-service='service'>\n" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java index 4e119506104..f0fcb239521 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java @@ -35,7 +35,7 @@ import static org.junit.Assert.assertTrue; public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBase { @Test - public void verify_that_overriding_connector_options_works() throws Exception { + public void verify_that_overriding_connector_options_works() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>\n" + " <http>\n" + @@ -56,7 +56,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas } @Test - public void verify_that_enabling_jetty_works() throws Exception { + public void verify_that_enabling_jetty_works() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>" + nodesXml + @@ -67,7 +67,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas } @Test - public void verify_that_enabling_jetty_works_for_custom_http_servers() throws Exception { + public void verify_that_enabling_jetty_works_for_custom_http_servers() { Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>", " <http>", @@ -80,7 +80,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas } @Test - public void verifyThatJettyHttpServerHasFilterBindingsProvider() throws Exception { + public void verifyThatJettyHttpServerHasFilterBindingsProvider() { final Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>", nodesXml, @@ -101,7 +101,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas } @Test - public void verifyThatJettyHttpServerHasFilterBindingsProviderForCustomHttpServers() throws Exception { + public void verifyThatJettyHttpServerHasFilterBindingsProviderForCustomHttpServers() { final Element clusterElem = DomBuilderTest.parse( "<container id='default' version='1.0'>", " <http>", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/RoutingBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/RoutingBuilderTest.java index 7789b4b8a16..6ebd530ca8c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/RoutingBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/RoutingBuilderTest.java @@ -64,7 +64,7 @@ public class RoutingBuilderTest extends ContainerModelBuilderTestBase { } - private ApplicationContainer getContainer(ApplicationPackage applicationPackage, String region, Element clusterElem) throws IOException, SAXException { + private ApplicationContainer getContainer(ApplicationPackage applicationPackage, String region, Element clusterElem) { DeployState deployState = new DeployState.Builder() .applicationPackage(applicationPackage) .zone(new Zone(Environment.prod, RegionName.from(region))) diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java index 0da3b8e1f5f..1f0b0188681 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java @@ -93,14 +93,14 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { // TODO: remove test when all containers are named 'container' @Test - public void cluster_with_only_search_gets_qrserver_as_service_name() throws Exception { + public void cluster_with_only_search_gets_qrserver_as_service_name() { createClusterWithOnlyDefaultChains(); ApplicationContainerCluster cluster = (ApplicationContainerCluster)root.getChildren().get("default"); assertThat(cluster.getContainers().get(0).getServiceName(), is(QRSERVER.serviceName)); } @Test - public void empty_search_element_gives_default_chains() throws Exception { + public void empty_search_element_gives_default_chains() { createClusterWithOnlyDefaultChains(); assertThat(chainsConfig().chains(), hasItemWithMethod("vespaPhases", "id")); assertThat(chainsConfig().chains(), hasItemWithMethod("native", "id")); @@ -137,7 +137,7 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { } @Test - public void cluster_is_connected_to_content_clusters() throws Exception { + public void cluster_is_connected_to_content_clusters() { String hosts = hostsXml(); String services = "" + @@ -163,7 +163,7 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { } @Test - public void cluster_is_connected_to_search_clusters() throws Exception { + public void cluster_is_connected_to_search_clusters() { String hosts = hostsXml(); String services = "" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java index c70e05c39c3..d98d1da9d2a 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java @@ -695,7 +695,7 @@ public class ContentClusterTest extends ContentBaseTest { } @Test - public void testConfiguredMetrics() throws Exception { + public void testConfiguredMetrics() { String xml = "" + "<services>" + "<content version=\"1.0\" id=\"storage\">\n" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java index 8ae68468374..f36ef6c3ba3 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/DistributorTest.java @@ -113,6 +113,7 @@ public class DistributorTest { assertEquals(true, conf.inlinebucketsplitting()); cluster = parseCluster("<cluster id=\"storage\">\n" + + " <redundancy>2</redundancy>" + " <documents/>" + " <tuning>" + " <distribution type=\"legacy\"/>" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/GenericConfigTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/GenericConfigTest.java index a65c4e50521..992edf6b1bb 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/GenericConfigTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/GenericConfigTest.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.model.content; import com.yahoo.vespa.config.content.StorFilestorConfig; -import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.content.storagecluster.StorageCluster; @@ -10,9 +9,6 @@ import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Before; import org.junit.Test; -import org.xml.sax.SAXException; - -import java.io.IOException; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -53,12 +49,12 @@ public class GenericConfigTest { } @Before - public void getVespaModel() throws IOException, SAXException, ParseException { + public void getVespaModel() { model = (new VespaModelCreatorWithMockPkg(ContentBaseTest.getHosts(), servicesXml(), ApplicationPackageUtils.generateSearchDefinitions("type1"))).create(); } @Test - public void config_override_on_root_is_visible_on_storage_cluster() throws Exception { + public void config_override_on_root_is_visible_on_storage_cluster() { StorageCluster cluster = model.getContentClusters().get("storage").getStorageNodes(); StorFilestorConfig config = model.getConfig(StorFilestorConfig.class, cluster.getConfigId()); @@ -66,7 +62,7 @@ public class GenericConfigTest { } @Test - public void config_override_on_root_is_visible_on_content_cluster() throws Exception { + public void config_override_on_root_is_visible_on_content_cluster() { ContentCluster cluster = model.getContentClusters().get("storage"); StorFilestorConfig config = model.getConfig(StorFilestorConfig.class, cluster.getConfigId()); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java index f402bae8fd9..afedbaea779 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexedHierarchicDistributionTest.java @@ -171,7 +171,7 @@ public class IndexedHierarchicDistributionTest { return createCluster(createClusterXml(groupXml, 2, 2)); } - private String getOddGroupsClusterXml() throws Exception { + private String getOddGroupsClusterXml() { return joinLines(" <group>", " <distribution partitions='2|*'/>", " <group distribution-key='0' name='group0'>", diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexingAndDocprocRoutingTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexingAndDocprocRoutingTest.java index e25bcea29f6..55d070d7247 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/IndexingAndDocprocRoutingTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/IndexingAndDocprocRoutingTest.java @@ -149,7 +149,7 @@ public class IndexingAndDocprocRoutingTest extends ContentBaseTest { } @Test - public void noContentClustersOneDocprocCluster() throws ParseException, IOException, SAXException { + public void noContentClustersOneDocprocCluster() { String services = "<?xml version='1.0' encoding='utf-8' ?>\n" + "<services version='1.0'>\n" + @@ -447,14 +447,12 @@ public class IndexingAndDocprocRoutingTest extends ContentBaseTest { searchClusterPost, searchClusterPostPost, mainPost, searchClusterSpecs); } - private VespaModel getIndexedSearchVespaModel(String xml) - throws ParseException, IOException, SAXException { + private VespaModel getIndexedSearchVespaModel(String xml) { List<String> sds = ApplicationPackageUtils.generateSearchDefinitions("music", "album", "artist"); return new VespaModelCreatorWithMockPkg(getHosts(), xml, sds).create(); } - private VespaModel getIndexedContentVespaModel(List<DocprocClusterSpec> docprocClusterSpecs, List<SearchClusterSpec> searchClusterSpecs) - throws ParseException, IOException, SAXException { + private VespaModel getIndexedContentVespaModel(List<DocprocClusterSpec> docprocClusterSpecs, List<SearchClusterSpec> searchClusterSpecs) { List<String> sds = new ArrayList<>(); for (SearchClusterSpec cluster : searchClusterSpecs) { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/MonitoringConfigSnoopTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/MonitoringConfigSnoopTest.java index dea86616f06..d73140d39d5 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/MonitoringConfigSnoopTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/MonitoringConfigSnoopTest.java @@ -17,7 +17,7 @@ public class MonitoringConfigSnoopTest { private TestRoot root; - public void initRoot(int interval) throws Exception { + public void initRoot(int interval) { TestDriver tester = new TestDriver(); root = tester.buildModel(getAdminXml(interval) + getContent()); } @@ -53,7 +53,7 @@ public class MonitoringConfigSnoopTest { } @Test - public void correct_config_is_snooped_default_interval() throws Exception { + public void correct_config_is_snooped_default_interval() { String getAdminXmlIntervalNotSpecified = "<admin version='2.0'>" + " <adminserver hostalias='mockhost' />" + "</admin>"; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/RedundancyTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/RedundancyTest.java index 3b2a741a177..eef48120da8 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/RedundancyTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/RedundancyTest.java @@ -22,9 +22,7 @@ public class RedundancyTest { } private static Redundancy createRedundancy(int redundancy, int implicitGroups, int totalNodes) { - Redundancy r = new Redundancy(1, redundancy, 1); - r.setImplicitGroups(implicitGroups); - r.setTotalNodes(totalNodes); + Redundancy r = new Redundancy(1, redundancy, 1, implicitGroups, totalNodes); return r; } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java index 27f5af52b6c..21c384dfc69 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java @@ -33,7 +33,7 @@ public class StorageClusterTest { MockRoot root = new MockRoot(); return parse(xml, root); } - StorageCluster parse(String xml, MockRoot root) throws Exception { + StorageCluster parse(String xml, MockRoot root) { root.getDeployState().getDocumentModel().getDocumentManager().add( new NewDocumentType(new NewDocumentType.Name("music")) ); @@ -190,7 +190,7 @@ public class StorageClusterTest { } @Test - public void testCapacity() throws Exception { + public void testCapacity() { String xml = "<cluster id=\"storage\">\n" + " <documents/>" + @@ -214,7 +214,7 @@ public class StorageClusterTest { } @Test - public void testRootFolder() throws Exception { + public void testRootFolder() { String xml = "<cluster id=\"storage\">\n" + " <documents/>" + @@ -245,7 +245,7 @@ public class StorageClusterTest { } @Test - public void testGenericPersistenceTuning() throws Exception { + public void testGenericPersistenceTuning() { String xml = "<cluster id=\"storage\">\n" + "<documents/>" + @@ -271,7 +271,7 @@ public class StorageClusterTest { } @Test - public void requireThatUserDoesNotSpecifyBothGroupAndNodes() throws Exception { + public void requireThatUserDoesNotSpecifyBothGroupAndNodes() { String xml = "<cluster id=\"storage\">\n" + "<documents/>\n" + @@ -302,10 +302,11 @@ public class StorageClusterTest { } @Test - public void requireThatGroupNamesMustBeUniqueAmongstSiblings() throws Exception { + public void requireThatGroupNamesMustBeUniqueAmongstSiblings() { String xml = "<cluster id=\"storage\">\n" + - "<documents/>\n" + + " <redundancy>2</redundancy>" + + " <documents/>\n" + " <group>\n" + " <distribution partitions=\"*\"/>\n" + " <group distribution-key=\"0\" name=\"bar\">\n" + @@ -327,9 +328,10 @@ public class StorageClusterTest { } @Test - public void requireThatGroupNamesCanBeDuplicatedAcrossLevels() throws Exception { + public void requireThatGroupNamesCanBeDuplicatedAcrossLevels() { String xml = "<cluster id=\"storage\">\n" + + " <redundancy>2</redundancy>" + "<documents/>\n" + " <group>\n" + " <distribution partitions=\"*\"/>\n" + @@ -351,7 +353,7 @@ public class StorageClusterTest { } @Test - public void requireThatNestedGroupsRequireDistribution() throws Exception { + public void requireThatNestedGroupsRequireDistribution() { String xml = "<cluster id=\"storage\">\n" + "<documents/>\n" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageContentTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageContentTest.java index 38f4b392f41..c0ddd49069d 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageContentTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageContentTest.java @@ -4,19 +4,23 @@ package com.yahoo.vespa.model.content; import com.yahoo.documentapi.messagebus.protocol.DocumentrouteselectorpolicyConfig; import com.yahoo.messagebus.routing.RouteSpec; import com.yahoo.messagebus.routing.RoutingTableSpec; -import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.routing.DocumentProtocol; import com.yahoo.vespa.model.routing.Routing; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Test; -import org.xml.sax.SAXException; -import java.io.IOException; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class StorageContentTest extends ContentBaseTest { // TODO: Test with document-definitions @@ -44,7 +48,7 @@ public class StorageContentTest extends ContentBaseTest { "</services>"; } - private VespaModel getStorageVespaModel(String cluster1docs, String cluster2docs) throws ParseException, IOException, SAXException { + private VespaModel getStorageVespaModel(String cluster1docs, String cluster2docs) { List<String> sds = ApplicationPackageUtils.generateSearchDefinitions("type1", "type2", "type3"); return new VespaModelCreatorWithMockPkg(getHosts(), createStorageVespaServices(cluster1docs, cluster2docs), sds).create(); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageGroupTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageGroupTest.java index 31c1e250183..cb457cabf6c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageGroupTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageGroupTest.java @@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue; */ public class StorageGroupTest { - ContentCluster parse(String xml) throws Exception { + ContentCluster parse(String xml) { return ContentClusterUtils.createCluster(xml, new MockRoot()); } @@ -80,6 +80,7 @@ public class StorageGroupTest { StorDistributionConfig.Builder builder = new StorDistributionConfig.Builder(); parse( "<content version=\"1.0\" id=\"storage\">\n" + + " <redundancy>4</redundancy>" + " <documents/>" + " <group>\n" + " <distribution partitions=\"1|*\"/>\n" + @@ -134,6 +135,7 @@ public class StorageGroupTest { StorDistributionConfig.Builder builder = new StorDistributionConfig.Builder(); parse( "<content version=\"1.0\" id=\"storage\">\n" + + " <redundancy>2</redundancy>" + " <documents/>" + " <group>\n" + " <distribution partitions=\"1|*\"/>\n" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java index fb54f8f9241..4fadea74feb 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/cluster/ClusterTest.java @@ -4,14 +4,11 @@ package com.yahoo.vespa.model.content.cluster; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.model.test.TestDriver; -import com.yahoo.searchdefinition.SearchBuilder; -import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.config.search.core.PartitionsConfig; import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.model.content.Content; -import com.yahoo.vespa.model.search.IndexedSearchCluster; -import com.yahoo.vespa.model.search.SearchDefinition; import com.yahoo.vespa.model.search.Dispatch; +import com.yahoo.vespa.model.search.IndexedSearchCluster; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import org.junit.Test; @@ -42,7 +39,7 @@ public class ClusterTest { } @Test - public void requireThatSearchCoverageIsApplied() throws ParseException { + public void requireThatSearchCoverageIsApplied() { ContentCluster cluster = newContentCluster(joinLines("<search>", " <coverage>", " <minimum>0.11</minimum>", @@ -127,6 +124,8 @@ public class ClusterTest { " </engine>", " <group>", " <node hostalias='my_host' distribution-key='0' />", + " <node hostalias='my_host' distribution-key='1' />", + " <node hostalias='my_host' distribution-key='2' />", " </group>", contentSearchXml, " </content>", @@ -142,13 +141,6 @@ public class ClusterTest { return "<document mode='index' type='my_document' " + (globalDocType ? "global='true' " : "") + "/>"; } - private static SearchDefinition newSearchDefinition(String name) throws ParseException { - SearchBuilder builder = new SearchBuilder(); - builder.importString("search " + name + " { document " + name + " { } }"); - builder.build(); - return new SearchDefinition(name, builder.getSearch(name)); - } - private static ProtonConfig getProtonConfig(ContentCluster cluster) { ProtonConfig.Builder builder = new ProtonConfig.Builder(); cluster.getSearch().getConfig(builder); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java index 95c57bb544c..866c03d82f0 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterBuilder.java @@ -77,7 +77,7 @@ public class ContentClusterBuilder { return this; } - public ContentCluster build(MockRoot root) throws Exception { + public ContentCluster build(MockRoot root) { return ContentClusterUtils.createCluster(getXml(), root); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/generic/GenericServicesTest.java b/config-model/src/test/java/com/yahoo/vespa/model/generic/GenericServicesTest.java index c6c08df64f4..0e7fce2d1ef 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/generic/GenericServicesTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/generic/GenericServicesTest.java @@ -2,17 +2,9 @@ package com.yahoo.vespa.model.generic; import com.yahoo.cloud.config.SentinelConfig; -import com.yahoo.config.codegen.CNode; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.application.provider.FilesApplicationPackage; -import com.yahoo.vespa.config.ConfigDefinitionKey; -import com.yahoo.vespa.config.ConfigPayload; -import com.yahoo.vespa.config.ConfigPayloadBuilder; -import com.yahoo.vespa.config.GenericConfig; -import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.generic.service.Service; -import com.yahoo.vespa.model.generic.service.ServiceCluster; import org.junit.BeforeClass; import org.junit.Test; import org.xml.sax.SAXException; @@ -40,7 +32,7 @@ public class GenericServicesTest { } @Test - public void testServicesSentinelConfig() throws IOException, SAXException { + public void testServicesSentinelConfig() { String sentinelConfigId1="hosts/bogusname1/sentinel"; String sentinelConfigId2="hosts/bogusname2/sentinel"; String sentinelConfigId3="hosts/bogusname3/sentinel"; @@ -72,7 +64,7 @@ public class GenericServicesTest { } @Test - public void testServicesModel() throws IOException, SAXException { + public void testServicesModel() { // Testing that this model can be constructed only for now } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/ml/ImportedModelTester.java b/config-model/src/test/java/com/yahoo/vespa/model/ml/ImportedModelTester.java index 41811738ea4..ce36ecc4a1c 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/ml/ImportedModelTester.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/ml/ImportedModelTester.java @@ -9,12 +9,10 @@ import com.yahoo.config.model.deploy.DeployState; import com.yahoo.io.GrowableByteBuffer; import com.yahoo.io.IOUtils; import com.yahoo.path.Path; -import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.RankingConstant; import ai.vespa.rankingexpression.importer.onnx.OnnxImporter; import ai.vespa.rankingexpression.importer.tensorflow.TensorFlowImporter; import ai.vespa.rankingexpression.importer.xgboost.XGBoostImporter; -import com.yahoo.searchdefinition.derived.RawRankProfile; import com.yahoo.tensor.Tensor; import com.yahoo.tensor.serialization.TypedBinaryFormat; import com.yahoo.vespa.model.VespaModel; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java index f50d41b9c95..3c5008cd5f6 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/MultilevelDispatchTest.java @@ -169,7 +169,6 @@ public class MultilevelDispatchTest { @Test public void requireThatMaxHitsIsScaled() throws Exception { ContentCluster cr = createCluster(getSimpleDispatchXml() + getMaxhitsTuning()); - IndexedSearchCluster ix = cr.getSearch().getIndexed(); Dispatch tld = cr.getSearch().getIndexed().getTLDs().get(0); PartitionsConfig.Builder builder = new PartitionsConfig.Builder(); tld.getConfig(builder); @@ -213,7 +212,7 @@ public class MultilevelDispatchTest { } @Test - public void requireThatSearchCoverageIsSetInSingleLevelSetup() throws Exception { + public void requireThatSearchCoverageIsSetInSingleLevelSetup() { TestRoot root = new TestDriver(true).buildModel(new MockApplicationPackage.Builder() .withServices("<services version='1.0'>" + "<content id='stateful' version='1.0'>" + @@ -370,7 +369,7 @@ public class MultilevelDispatchTest { } @Test - public void requireThatWeReferenceValidNodesWhenSettingUpDispatchGroups() throws Exception { + public void requireThatWeReferenceValidNodesWhenSettingUpDispatchGroups() { try { createIllegalSetupWithIllegalNodeReference(); assertFalse("Did not get expected Exception", true); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/NodeFlavorTuningTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/NodeFlavorTuningTest.java index a4fac3fc9c6..a4b414ba0da 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/search/NodeFlavorTuningTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/NodeFlavorTuningTest.java @@ -33,7 +33,7 @@ public class NodeFlavorTuningTest { assertEquals(24 * GB, cfg.hwinfo().memory().size()); } - private ProtonConfig getProtonMemoryConfig(List<Pair<String, String>> sdAndMode, int gb) { + private ProtonConfig getProtonMemoryConfig(List<Pair<String, String>> sdAndMode, int gb, int redundancy, int searchableCopies) { ProtonConfig.Builder builder = new ProtonConfig.Builder(); for (Pair<String, String> sdMode : sdAndMode) { builder.documentdb.add(new ProtonConfig.Documentdb.Builder() @@ -41,19 +41,26 @@ public class NodeFlavorTuningTest { .configid("some/config/id/" + sdMode.getFirst()) .mode(ProtonConfig.Documentdb.Mode.Enum.valueOf(sdMode.getSecond()))); } - return configFromMemorySetting(gb, builder); + return configFromMemorySetting(gb, builder, redundancy, searchableCopies); } - @Test - public void require_that_initial_numdocs_is_dependent_of_mode() { - ProtonConfig cfg = getProtonMemoryConfig(Arrays.asList(new Pair<>("a", "INDEX"), new Pair<>("b", "STREAMING"), new Pair<>("c", "STORE_ONLY")), 24); + private void verify_that_initial_numdocs_is_dependent_of_mode(int redundancy, int searchablecopies) { + int divisor = Math.max(redundancy, searchablecopies); + ProtonConfig cfg = getProtonMemoryConfig(Arrays.asList(new Pair<>("a", "INDEX"), new Pair<>("b", "STREAMING"), new Pair<>("c", "STORE_ONLY")), 24, redundancy, searchablecopies); assertEquals(3, cfg.documentdb().size()); assertEquals(1024, cfg.documentdb(0).allocation().initialnumdocs()); assertEquals("a", cfg.documentdb(0).inputdoctypename()); - assertEquals(402653184, cfg.documentdb(1).allocation().initialnumdocs()); + assertEquals(402653184/divisor, cfg.documentdb(1).allocation().initialnumdocs()); assertEquals("b", cfg.documentdb(1).inputdoctypename()); - assertEquals(402653184, cfg.documentdb(2).allocation().initialnumdocs()); + assertEquals(402653184/divisor, cfg.documentdb(2).allocation().initialnumdocs()); assertEquals("c", cfg.documentdb(2).inputdoctypename()); } + @Test + public void require_that_initial_numdocs_is_dependent_of_mode_and_searchablecopies() { + verify_that_initial_numdocs_is_dependent_of_mode(2,0); + verify_that_initial_numdocs_is_dependent_of_mode(1,1); + verify_that_initial_numdocs_is_dependent_of_mode(3, 2); + verify_that_initial_numdocs_is_dependent_of_mode(3, 3); + } @Test public void require_that_hwinfo_cpu_cores_is_set() { @@ -183,9 +190,9 @@ public class NodeFlavorTuningTest { return getConfig(new FlavorsConfig.Flavor.Builder(). minMainMemoryAvailableGb(memoryGb)); } - private static ProtonConfig configFromMemorySetting(int memoryGb, ProtonConfig.Builder builder) { + private static ProtonConfig configFromMemorySetting(int memoryGb, ProtonConfig.Builder builder, int redundancy, int searchableCopies) { return getConfig(new FlavorsConfig.Flavor.Builder(). - minMainMemoryAvailableGb(memoryGb), builder); + minMainMemoryAvailableGb(memoryGb), builder, redundancy, searchableCopies); } private static ProtonConfig configFromNumCoresSetting(double numCores) { @@ -198,16 +205,14 @@ public class NodeFlavorTuningTest { } private static ProtonConfig getConfig(FlavorsConfig.Flavor.Builder flavorBuilder) { - getConfig(flavorBuilder, new ProtonConfig.Builder()); - flavorBuilder.name("my_flavor"); - NodeFlavorTuning tuning = new NodeFlavorTuning(new Flavor(new FlavorsConfig.Flavor(flavorBuilder))); - ProtonConfig.Builder protonBuilder = new ProtonConfig.Builder(); - tuning.getConfig(protonBuilder); - return new ProtonConfig(protonBuilder); + return getConfig(flavorBuilder, new ProtonConfig.Builder()); } private static ProtonConfig getConfig(FlavorsConfig.Flavor.Builder flavorBuilder, ProtonConfig.Builder protonBuilder) { + return getConfig(flavorBuilder, protonBuilder, 1,1); + } + private static ProtonConfig getConfig(FlavorsConfig.Flavor.Builder flavorBuilder, ProtonConfig.Builder protonBuilder, int redundancy, int searchableCopies) { flavorBuilder.name("my_flavor"); - NodeFlavorTuning tuning = new NodeFlavorTuning(new Flavor(new FlavorsConfig.Flavor(flavorBuilder))); + NodeFlavorTuning tuning = new NodeFlavorTuning(new Flavor(new FlavorsConfig.Flavor(flavorBuilder)), redundancy, searchableCopies); tuning.getConfig(protonBuilder); return new ProtonConfig(protonBuilder); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java index c43787b37e0..b2c7da4b851 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java @@ -7,7 +7,6 @@ import com.yahoo.vespa.config.search.core.ProtonConfig; import com.yahoo.vespa.config.search.RankProfilesConfig; import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig; import com.yahoo.search.config.IndexInfoConfig; -import com.yahoo.searchdefinition.parser.ParseException; import com.yahoo.vespa.config.search.AttributesConfig; import com.yahoo.vespa.configdefinition.IlscriptsConfig; import com.yahoo.vespa.model.VespaModel; @@ -17,8 +16,7 @@ import com.yahoo.vespa.model.search.IndexedSearchCluster; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Test; -import org.xml.sax.SAXException; -import java.io.IOException; + import java.util.ArrayList; import java.util.List; import java.util.Arrays; @@ -103,7 +101,7 @@ public class DocumentDatabaseTestCase { assertEquals(type1Id, proton.documentdb(0).configid()); } @Test - public void requireThatWeCanHaveOneSDForIndexedMode() throws IOException, SAXException, ParseException { + public void requireThatWeCanHaveOneSDForIndexedMode() { assertSingleSD("index"); } @@ -118,13 +116,13 @@ public class DocumentDatabaseTestCase { @Test public void requireThatConcurrencyIsReflectedCorrectlyForDefault() { - verifyConcurrency("index", "", 0.25, 0.25); - verifyConcurrency("streaming", "", 0.5, 0.0); - verifyConcurrency("store-only", "", 0.5, 0.0); + verifyConcurrency("index", "", 0.30, 0.30); + verifyConcurrency("streaming", "", 0.6, 0.0); + verifyConcurrency("store-only", "", 0.6, 0.0); } @Test public void requireThatMixedModeConcurrencyIsReflectedCorrectlyForDefault() { - verifyConcurrency(Arrays.asList(DocType.create("a", "index"), DocType.create("b", "streaming")), "", 0.5, Arrays.asList(0.25, 0.0)); + verifyConcurrency(Arrays.asList(DocType.create("a", "index"), DocType.create("b", "streaming")), "", 0.6, Arrays.asList(0.30, 0.0)); } @Test public void requireThatMixedModeConcurrencyIsReflected() { @@ -213,7 +211,7 @@ public class DocumentDatabaseTestCase { } @Test - public void requireThatWeCanHaveMultipleSearchDefinitions() throws IOException, SAXException, ParseException { + public void requireThatWeCanHaveMultipleSearchDefinitions() { final List<String> sds = Arrays.asList("type1", "type2", "type3"); VespaModel model = new VespaModelCreatorWithMockPkg(vespaHosts, createVespaServices(sds, "index"), ApplicationPackageUtils.generateSearchDefinitions(sds)).create(); @@ -263,7 +261,7 @@ public class DocumentDatabaseTestCase { } @Test - public void requireThatRelevantConfigIsAvailableForClusterSearcher() throws ParseException, IOException, SAXException { + public void requireThatRelevantConfigIsAvailableForClusterSearcher() { final List<String> sds = Arrays.asList("type1", "type2"); VespaModel model = new VespaModelCreatorWithMockPkg(vespaHosts, createVespaServices(sds, "index"), ApplicationPackageUtils.generateSearchDefinitions(sds)).create(); @@ -336,7 +334,7 @@ public class DocumentDatabaseTestCase { } @Test - public void requireThatDocumentDBConfigIsAvailableForStreaming() throws ParseException, IOException, SAXException { + public void requireThatDocumentDBConfigIsAvailableForStreaming() { assertDocumentDBConfigAvailableForStreaming("streaming"); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java index e39e0147dbf..007dc5d7266 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/search/test/SearchClusterTest.java @@ -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.model.search.test; -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.container.QrSearchersConfig; import com.yahoo.document.DataType; import com.yahoo.search.config.ClusterConfig; @@ -19,9 +17,6 @@ import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Test; -import org.xml.sax.SAXException; - -import java.io.IOException; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; @@ -49,7 +44,7 @@ public class SearchClusterTest { "</hosts>"; @Test - public void testSdConfigLogical() throws IOException, SAXException { + public void testSdConfigLogical() { // sd1 SDDocumentType sdt1=new SDDocumentType("s1"); Search search1 = new Search("s1", null); @@ -75,7 +70,7 @@ public class SearchClusterTest { } @Test - public void search_model_is_connected_to_container_clusters_two_content_clusters() throws Exception { + public void search_model_is_connected_to_container_clusters_two_content_clusters() { String services = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<services version=\"1.0\">" + " <admin version='2.0'>" + diff --git a/config-model/src/test/java/com/yahoo/vespa/model/storage/test/StorageModelTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/storage/test/StorageModelTestCase.java index 19d46ea8f11..e4dd8aee607 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/storage/test/StorageModelTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/storage/test/StorageModelTestCase.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertThat; public class StorageModelTestCase { @Test(expected=RuntimeException.class) - public void testTwoClustersSameName() throws Exception { + public void testTwoClustersSameName() { createModel("src/test/cfg/storage/twoclusterssamename"); } @@ -31,7 +31,7 @@ public class StorageModelTestCase { } @Test - public void testIndexGreaterThanNumNodes() throws Exception { + public void testIndexGreaterThanNumNodes() { VespaModel vespaModel = createModel("src/test/cfg/storage/app_index_higher_than_num_nodes"); // Test fleet controller config @@ -43,7 +43,7 @@ public class StorageModelTestCase { } @Test - public void testMetricsSnapshotIntervalYAMAS() throws Exception { + public void testMetricsSnapshotIntervalYAMAS() { VespaModel vespaModel = createModel("src/test/cfg/storage/clustercontroller_advanced"); ContentCluster contentCluster = vespaModel.getContentClusters().values().iterator().next(); assertNotNull(contentCluster); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/ModelAmendingTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/test/ModelAmendingTestCase.java index 1ce584c2910..a8e946bcea1 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/ModelAmendingTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/ModelAmendingTestCase.java @@ -20,9 +20,7 @@ import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; import com.yahoo.vespa.model.content.Content; import org.junit.Test; import org.w3c.dom.Element; -import org.xml.sax.SAXException; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -42,7 +40,7 @@ import static org.junit.Assert.assertNotNull; public class ModelAmendingTestCase { @Test - public void testModelAmending() throws IOException, SAXException { + public void testModelAmending() { ConfigModelRegistry amendingModelRepo = MapConfigModelRegistry.createFromList(new AdminModelAmenderBuilder(), new ContainerModelAmenderBuilder(), new ContentModelAmenderBuilder()); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/ParentService.java b/config-model/src/test/java/com/yahoo/vespa/model/test/ParentService.java index c7559c68592..b7887262ed5 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/ParentService.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/ParentService.java @@ -13,8 +13,6 @@ import org.w3c.dom.NodeList; */ public class ParentService extends AbstractService implements com.yahoo.test.StandardConfig.Producer { - public int childCnt = 0; - /** * Creates a new ParentService instance * diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/PortsMetaTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/test/PortsMetaTestCase.java index 63c15bf2c13..7736bea3078 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/PortsMetaTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/PortsMetaTestCase.java @@ -17,7 +17,7 @@ import static org.junit.Assert.assertTrue; public class PortsMetaTestCase { @Test - public void testRegister() throws Exception { + public void testRegister() { PortsMeta pm = new PortsMeta(); pm.on(0).tag("foo"); pm.on(1).tag("bar"); @@ -31,7 +31,7 @@ public class PortsMetaTestCase { } @Test - public void testAdminStatusApi() throws Exception { + public void testAdminStatusApi() { PortsMeta pm = new PortsMeta() .on(0).tag("rpc").tag("nc").tag("admin").tag("status") .on(1).tag("rpc").tag("rtx").tag("admin").tag("status") diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java index 3fa013d0089..ac81c1a7b7e 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java @@ -28,7 +28,7 @@ import com.yahoo.vespa.model.ConfigProducer; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.admin.Admin; import com.yahoo.vespa.model.application.validation.Validation; -import com.yahoo.vespa.model.test.utils.CommonVespaModelSetup; +import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Ignore; @@ -66,13 +66,9 @@ public class VespaModelTestCase { "</host>" + "</hosts>"; - public static VespaModel getVespaModel(String configPath) { - return getVespaModel(configPath, true); - } - - public static VespaModel getVespaModel(String configPath, boolean validateXml) { + private static VespaModel getVespaModel(String configPath) { VespaModelCreatorWithFilePkg creator = new VespaModelCreatorWithFilePkg(configPath); - return creator.create(validateXml); + return creator.create(true); } // Debugging @@ -89,7 +85,7 @@ public class VespaModelTestCase { // Verify that common config from plugins is delivered from the root node for any configId, using the Builder based API @Test - public void testCommonConfig() throws Exception { + public void testCommonConfig() { VespaModel model = getVespaModel(TESTDIR + "app_nohosts/"); LogdConfig.Builder b = new LogdConfig.Builder(); b = (LogdConfig.Builder) model.getConfig(b, ""); @@ -135,7 +131,7 @@ public class VespaModelTestCase { } @Test - public void testHostsOverrides() throws IOException, SAXException { + public void testHostsOverrides() { VespaModel model = new VespaModelCreatorWithMockPkg( simpleHosts, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + @@ -171,8 +167,8 @@ public class VespaModelTestCase { } @Test - public void testCreateFromReaders() throws SAXException, IOException { - VespaModel model = CommonVespaModelSetup.createVespaModelWithMusic( + public void testCreateFromReaders() { + VespaModel model = new VespaModelCreatorWithMockPkg( simpleHosts, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<services version=\"1.0\">" + @@ -195,7 +191,9 @@ public class VespaModelTestCase { " <document type=\"music\" mode=\"index\"/>" + " </documents>" + "</content>" + - "</services>"); + "</services>", + ApplicationPackageUtils.generateSearchDefinition("music")) + .create(); MessagebusConfig.Builder mBusB = new MessagebusConfig.Builder(); model.getConfig(mBusB, "client"); MessagebusConfig mBus = new MessagebusConfig(mBusB); @@ -230,7 +228,7 @@ public class VespaModelTestCase { public void testDeployLogger() throws IOException, SAXException { final String services = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<services version=\"1.0\">" + - "<config name=\"unknsownfoo\">" + + "<config name=\"bar.unknsownfoo\">" + "<logserver><host>foo</host></logserver>" + "</config>" + "<admin version=\"2.0\">" + @@ -253,11 +251,12 @@ public class VespaModelTestCase { @Test public void testNoAdmin() { - VespaModel model = CommonVespaModelSetup.createVespaModelWithMusic( + VespaModel model = new VespaModelCreatorWithMockPkg( simpleHosts, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<services version=\"1.0\">" + - "</services>"); + "</services>") + .create(); Admin admin = model.getAdmin(); assertThat(admin.getSlobroks().size(), is(1)); assertThat(admin.getConfigservers().size(), is(1)); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/utils/ApplicationPackageUtils.java b/config-model/src/test/java/com/yahoo/vespa/model/test/utils/ApplicationPackageUtils.java index a58b6a2e372..ee6fc60ba46 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/utils/ApplicationPackageUtils.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/utils/ApplicationPackageUtils.java @@ -1,11 +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.model.test.utils; -import com.yahoo.searchdefinition.Search; -import com.yahoo.searchdefinition.SearchBuilder; -import com.yahoo.searchdefinition.parser.ParseException; -import com.yahoo.vespa.model.search.SearchDefinition; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -48,18 +43,6 @@ public class ApplicationPackageUtils { "}"; } - public static Search createSearch(String name, String field1, String field2) throws ParseException { - SearchBuilder sb = new SearchBuilder(); - sb.importString(generateSearchDefinition(name, field1, field2)); - sb.build(); - return sb.getSearch(); - } - - public static SearchDefinition createSearchDefinition(String name, String field1, String field2) throws ParseException { - com.yahoo.searchdefinition.Search type = ApplicationPackageUtils.createSearch(name, field1, field2); - return new SearchDefinition(type.getName(), type); - } - public static List<String> generateSearchDefinition(String name) { return generateSearchDefinitions(name); } @@ -68,20 +51,6 @@ public class ApplicationPackageUtils { return generateSearchDefinitions(Arrays.asList(sdNames)); } - public static List<SearchDefinition> createSearchDefinition(String name) throws ParseException { - return createSearchDefinitions(Arrays.asList(name)); - } - - public static List<SearchDefinition> createSearchDefinitions(List<String> sdNames) throws ParseException { - List<SearchDefinition> sds = new ArrayList<>(); - int i = 0; - for (String sdName : sdNames) { - sds.add(createSearchDefinition(sdName, "f" + (i + 1), "f" + (i + 2))); - i = i + 2; - } - return sds; - } - public static List<String> generateSearchDefinitions(List<String> sdNames) { List<String> sds = new ArrayList<>(); int i = 0; diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/utils/CommonVespaModelSetup.java b/config-model/src/test/java/com/yahoo/vespa/model/test/utils/CommonVespaModelSetup.java deleted file mode 100644 index 3791331e40c..00000000000 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/utils/CommonVespaModelSetup.java +++ /dev/null @@ -1,34 +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.model.test.utils; - -import com.yahoo.config.application.api.ApplicationPackage; -import com.yahoo.config.model.test.MockApplicationPackage; -import com.yahoo.vespa.model.VespaModel; - -import java.io.File; - -/** - * @author Tony Vaagenes - */ -//TODO Remove, use VespaModelCreatorWithMockPkg or VespaModelCreatorWithFilePkg instead -public class CommonVespaModelSetup { - - public static VespaModel createVespaModelWithMusic(String path) { - return createVespaModelWithMusic(new File(path)); - } - - public static VespaModel createVespaModelWithMusic(File dir) { - VespaModelCreatorWithFilePkg modelCreator = new VespaModelCreatorWithFilePkg(dir); - return modelCreator.create(); - } - - public static VespaModel createVespaModelWithMusic(String hosts, String services) { - ApplicationPackage app = new MockApplicationPackage.Builder() - .withHosts(hosts) - .withServices(services) - .withSearchDefinition(MockApplicationPackage.MUSIC_SEARCHDEFINITION) - .build(); - VespaModelCreatorWithMockPkg modelCreator = new VespaModelCreatorWithMockPkg(app); - return modelCreator.create(); - } -} diff --git a/config-provisioning/abi-spec.json b/config-provisioning/abi-spec.json index 64114389751..33c02811318 100644 --- a/config-provisioning/abi-spec.json +++ b/config-provisioning/abi-spec.json @@ -737,25 +737,6 @@ ], "fields": [] }, - "com.yahoo.config.provision.RotationName": { - "superClass": "java.lang.Object", - "interfaces": [ - "java.lang.Comparable" - ], - "attributes": [ - "public" - ], - "methods": [ - "public java.lang.String value()", - "public boolean equals(java.lang.Object)", - "public int hashCode()", - "public int compareTo(com.yahoo.config.provision.RotationName)", - "public java.lang.String toString()", - "public static com.yahoo.config.provision.RotationName from(java.lang.String)", - "public bridge synthetic int compareTo(java.lang.Object)" - ], - "fields": [] - }, "com.yahoo.config.provision.SystemName": { "superClass": "java.lang.Enum", "interfaces": [], diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java deleted file mode 100644 index 5d9ac3699b3..00000000000 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.config.provision; - -import java.util.Objects; - -/** - * Represents a rotation name for a container cluster. Typically created from the rotation element in services.xml. - * - * @author mpolden - */ -public class RotationName implements Comparable<RotationName> { - - private final String name; - - private RotationName(String name) { - this.name = requireNonBlank(name, "name must be non-empty"); - } - - public String value() { - return name; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RotationName that = (RotationName) o; - return name.equals(that.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - - @Override - public int compareTo(RotationName o) { - return name.compareTo(o.name); - } - - @Override - public String toString() { - return "rotation '" + name + "'"; - } - - public static RotationName from(String name) { - return new RotationName(name); - } - - private static String requireNonBlank(String s, String message) { - if (s == null || s.isBlank()) { - throw new IllegalArgumentException(message); - } - return s; - } - -} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceException.java b/config-provisioning/src/main/java/com/yahoo/config/provision/exception/LoadBalancerServiceException.java index e5ab519ab94..41aa241621d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceException.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/exception/LoadBalancerServiceException.java @@ -1,10 +1,10 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.provision.lb; +package com.yahoo.config.provision.exception; import com.yahoo.config.provision.TransientException; /** - * Transient exception thrown on behalf of a {@link LoadBalancerService}. + * Transient exception thrown on behalf of a load balancer service * * @author mpolden */ diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/exception/package-info.java b/config-provisioning/src/main/java/com/yahoo/config/provision/exception/package-info.java new file mode 100644 index 00000000000..5730f3fdb6b --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/exception/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.config.provision.exception; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java index 5ffc7293742..e815fd9a5f9 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyRpcServer.java @@ -133,7 +133,6 @@ public class ConfigProxyRpcServer implements Runnable, TargetWatcher, RpcServer dispatchRpcRequest(req, () -> { JRTServerConfigRequest request = JRTServerConfigRequestV3.createFromRequest(req); if (isProtocolVersionSupported(request)) { - proxyServer.getStatistics().incRpcRequests(); req.target().addWatcher(this); getConfigImpl(request); return; diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyStatistics.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyStatistics.java deleted file mode 100644 index 314a4b0cb11..00000000000 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ConfigProxyStatistics.java +++ /dev/null @@ -1,104 +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.proxy; - -import com.yahoo.log.LogLevel; -import com.yahoo.log.event.Event; - -/** - * Statistics/metrics for config proxy. - * //TODO Use metrics framework - * - * @author hmusum - */ -class ConfigProxyStatistics implements Runnable { - static final long defaultEventInterval = 5 * 60; // in seconds - - private final long eventInterval; // in seconds - private boolean stopped; - private long lastRun = System.currentTimeMillis(); - - /* Number of RPC getConfig requests */ - private long rpcRequests = 0; - private long processedRequests = 0; - private long errors = 0; - private long delayedResponses = 0; - - ConfigProxyStatistics() { - this(defaultEventInterval); - } - - ConfigProxyStatistics(long eventInterval) { - this.eventInterval = eventInterval; - } - - // Send events every eventInterval seconds - public void run() { - while (true) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - ProxyServer.log.log(LogLevel.WARNING, e.getMessage()); - } - if (stopped) { - return; - } - ProxyServer.log.log(LogLevel.SPAM, "Running ConfigProxyStatistics"); - // Only send events every eventInterval seconds - if ((System.currentTimeMillis() - lastRun) > eventInterval * 1000) { - lastRun = System.currentTimeMillis(); - sendEvents(); - } - } - } - - private void sendEvents() { - Event.count("rpc_requests", rpcRequests()); - Event.count("processed_messages", processedRequests()); - Event.count("errors", errors()); - Event.value("delayed_responses", delayedResponses()); - } - - void stop() { - stopped = true; - } - - Long getEventInterval() { - return eventInterval; - } - - void incRpcRequests() { - rpcRequests++; - } - - void incProcessedRequests() { - processedRequests++; - } - - void incErrorCount() { - errors++; - } - - long processedRequests() { - return processedRequests; - } - - long rpcRequests() { - return rpcRequests; - } - - long errors() { - return errors; - } - - long delayedResponses() { - return delayedResponses; - } - - void delayedResponses(long count) { - delayedResponses = count; - } - - void decDelayedResponses() { - delayedResponses--; - } -} diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/Mode.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/Mode.java index da6e7d975cb..fdc2b886701 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/Mode.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/Mode.java @@ -31,9 +31,6 @@ class Mode { Mode(String modeString) { switch (modeString.toLowerCase()) { - case "" : - mode = ModeName.DEFAULT; - break; case "default" : mode = ModeName.DEFAULT; break; @@ -41,7 +38,7 @@ class Mode { mode = ModeName.MEMORYCACHE; break; default: - throw new IllegalArgumentException("Unrecognized mode'" + modeString + "' supplied"); + throw new IllegalArgumentException("Unrecognized mode '" + modeString + "' supplied"); } } diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java index 4bf3bd5a786..f4f2308f261 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/ProxyServer.java @@ -28,7 +28,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; /** * A proxy server that handles RPC config requests. The proxy can run in two modes: * 'default' and 'memorycache', where the last one will not get config from an upstream - * config source, but will serve config only from memory cache. + * config source, but will serve config from memory cache only. * * @author hmusum */ @@ -38,7 +38,7 @@ public class ProxyServer implements Runnable { private static final int JRT_TRANSPORT_THREADS = 4; static final String DEFAULT_PROXY_CONFIG_SOURCES = "tcp/localhost:19070"; - final static Logger log = Logger.getLogger(ProxyServer.class.getName()); + private final static Logger log = Logger.getLogger(ProxyServer.class.getName()); private final AtomicBoolean signalCaught = new AtomicBoolean(false); // Scheduled executor that periodically checks for requests that have timed out and response should be returned to clients @@ -52,7 +52,6 @@ public class ProxyServer implements Runnable { private volatile ConfigSourceClient configClient; - private final ConfigProxyStatistics statistics; private final TimingValues timingValues; private final MemoryCache memoryCache; private static final double timingValuesRatio = 0.8; @@ -72,33 +71,28 @@ public class ProxyServer implements Runnable { defaultTimingValues = tv; } - private ProxyServer(Spec spec, DelayedResponses delayedResponses, ConfigSourceSet source, - ConfigProxyStatistics statistics, TimingValues timingValues, - boolean delayedResponseHandling, MemoryCache memoryCache, - ConfigSourceClient configClient) { - this.delayedResponses = delayedResponses; + private ProxyServer(Spec spec, ConfigSourceSet source, TimingValues timingValues, + boolean delayedResponseHandling, MemoryCache memoryCache, ConfigSourceClient configClient) { + this.delayedResponses = new DelayedResponses(); this.configSource = source; log.log(LogLevel.DEBUG, "Using config source '" + source); - this.statistics = statistics; this.timingValues = timingValues; this.delayedResponseHandling = delayedResponseHandling; this.memoryCache = memoryCache; this.rpcServer = createRpcServer(spec); - this.configClient = createClient(rpcServer, statistics, delayedResponses, source, timingValues, memoryCache, configClient); + this.configClient = createClient(rpcServer, delayedResponses, source, timingValues, memoryCache, configClient); this.fileDistributionAndUrlDownload = new FileDistributionAndUrlDownload(supervisor, source); } static ProxyServer createTestServer(ConfigSourceSet source) { - return createTestServer(source, null, new MemoryCache(), new ConfigProxyStatistics()); + return createTestServer(source, null, new MemoryCache()); } static ProxyServer createTestServer(ConfigSourceSet source, ConfigSourceClient configSourceClient, - MemoryCache memoryCache, - ConfigProxyStatistics statistics) { + MemoryCache memoryCache) { final boolean delayedResponseHandling = false; - return new ProxyServer(null, new DelayedResponses(), - source, statistics, defaultTimingValues(), delayedResponseHandling, + return new ProxyServer(null, source, defaultTimingValues(), delayedResponseHandling, memoryCache, configSourceClient); } @@ -120,7 +114,6 @@ public class ProxyServer implements Runnable { } RawConfig resolveConfig(JRTServerConfigRequest req) { - statistics.incProcessedRequests(); // Calling getConfig() will either return with an answer immediately or // create a background thread that retrieves config from the server and // calls updateSubscribers when new config is returned from the config source. @@ -155,12 +148,11 @@ public class ProxyServer implements Runnable { } } - private ConfigSourceClient createClient(RpcServer rpcServer, ConfigProxyStatistics statistics, - DelayedResponses delayedResponses, + private ConfigSourceClient createClient(RpcServer rpcServer, DelayedResponses delayedResponses, ConfigSourceSet source, TimingValues timingValues, MemoryCache memoryCache, ConfigSourceClient client) { return (client == null) - ? new RpcConfigSourceClient(rpcServer, source, statistics, memoryCache, timingValues, delayedResponses) + ? new RpcConfigSourceClient(rpcServer, source, memoryCache, timingValues, delayedResponses) : client; } @@ -169,7 +161,7 @@ public class ProxyServer implements Runnable { } private RpcConfigSourceClient createRpcClient() { - return new RpcConfigSourceClient(rpcServer, configSource, statistics, memoryCache, timingValues, delayedResponses); + return new RpcConfigSourceClient(rpcServer, configSource, memoryCache, timingValues, delayedResponses); } private void setupSignalHandler() { @@ -202,15 +194,9 @@ public class ProxyServer implements Runnable { port = Integer.parseInt(args[0]); } Event.started("configproxy"); - ConfigProxyStatistics statistics = new ConfigProxyStatistics(properties.eventInterval); - Thread t = new Thread(statistics); - t.setName("Metrics generator"); - t.setDaemon(true); - t.start(); ConfigSourceSet configSources = new ConfigSourceSet(properties.configSources); - DelayedResponses delayedResponses = new DelayedResponses(); - ProxyServer proxyServer = new ProxyServer(new Spec(null, port), delayedResponses, configSources, statistics, + ProxyServer proxyServer = new ProxyServer(new Spec(null, port), configSources, defaultTimingValues(), true, new MemoryCache(), null); // catch termination and interrupt signal proxyServer.setupSignalHandler(); @@ -221,18 +207,14 @@ public class ProxyServer implements Runnable { } static Properties getSystemProperties() { - // Read system properties - long eventInterval = Long.getLong("eventinterval", ConfigProxyStatistics.defaultEventInterval); final String[] inputConfigSources = System.getProperty("proxyconfigsources", DEFAULT_PROXY_CONFIG_SOURCES).split(","); - return new Properties(eventInterval, inputConfigSources); + return new Properties(inputConfigSources); } static class Properties { - final long eventInterval; final String[] configSources; - Properties(long eventInterval, String[] configSources) { - this.eventInterval = eventInterval; + Properties(String[] configSources) { this.configSources = configSources; } } @@ -245,10 +227,6 @@ public class ProxyServer implements Runnable { return timingValues; } - ConfigProxyStatistics getStatistics() { - return statistics; - } - // Cancels all config instances and flushes the cache. When this method returns, // the cache will not be updated again before someone calls getConfig(). private synchronized void flush() { @@ -261,9 +239,6 @@ public class ProxyServer implements Runnable { if (rpcServer != null) rpcServer.shutdown(); if (delayedResponseScheduler != null) delayedResponseScheduler.cancel(true); flush(); - if (statistics != null) { - statistics.stop(); - } fileDistributionAndUrlDownload.close(); } diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java index c9f43ac48e2..d809a3c97ed 100644 --- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java +++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClient.java @@ -38,7 +38,6 @@ class RpcConfigSourceClient implements ConfigSourceClient { private final HashMap<ConfigCacheKey, Subscriber> activeSubscribers = new HashMap<>(); private final Object activeSubscribersLock = new Object(); private final MemoryCache memoryCache; - private final ConfigProxyStatistics statistics; private final DelayedResponses delayedResponses; private final TimingValues timingValues; @@ -48,13 +47,11 @@ class RpcConfigSourceClient implements ConfigSourceClient { RpcConfigSourceClient(RpcServer rpcServer, ConfigSourceSet configSourceSet, - ConfigProxyStatistics statistics, MemoryCache memoryCache, TimingValues timingValues, DelayedResponses delayedResponses) { this.rpcServer = rpcServer; this.configSourceSet = configSourceSet; - this.statistics = statistics; this.memoryCache = memoryCache; this.delayedResponses = delayedResponses; this.timingValues = timingValues; @@ -122,7 +119,6 @@ class RpcConfigSourceClient implements ConfigSourceClient { // happens at the same time DelayedResponse delayedResponse = new DelayedResponse(request); delayedResponses.add(delayedResponse); - statistics.delayedResponses(delayedResponses.size()); final ConfigCacheKey configCacheKey = new ConfigCacheKey(input.getKey(), input.getDefMd5()); RawConfig cachedConfig = memoryCache.get(configCacheKey); @@ -139,7 +135,6 @@ class RpcConfigSourceClient implements ConfigSourceClient { // unless another thread already did it ret = cachedConfig; } - statistics.decDelayedResponses(); } if (!cachedConfig.isError() && cachedConfig.getGeneration() > 0) { needToGetConfig = false; @@ -220,7 +215,6 @@ class RpcConfigSourceClient implements ConfigSourceClient { */ public void updateSubscribers(RawConfig config) { log.log(LogLevel.DEBUG, () -> "Config updated for " + config.getKey() + "," + config.getGeneration()); - if (config.isError()) { statistics.incErrorCount(); } DelayQueue<DelayedResponse> responseDelayQueue = delayedResponses.responses(); log.log(LogLevel.SPAM, () -> "Delayed response queue: " + responseDelayQueue); if (responseDelayQueue.size() == 0) { diff --git a/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ModeTest.java b/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ModeTest.java deleted file mode 100644 index 1a6bbd11b59..00000000000 --- a/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ModeTest.java +++ /dev/null @@ -1,61 +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.proxy; - -import org.junit.Test; - -import java.util.HashSet; -import java.util.Set; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -/** - * @author hmusum - */ -public class ModeTest { - - @Test - public void basic() { - Mode mode = new Mode(); - assertModeName(Mode.ModeName.DEFAULT, mode); - assertTrue(mode.isDefault()); - - mode = new Mode(""); - assertModeName(Mode.ModeName.DEFAULT, mode); - assertTrue(mode.isDefault()); - - mode = new Mode(Mode.ModeName.DEFAULT.name()); - assertModeName(Mode.ModeName.DEFAULT, mode); - assertTrue(mode.isDefault()); - - mode = new Mode(Mode.ModeName.MEMORYCACHE.name()); - assertModeName(Mode.ModeName.MEMORYCACHE, mode); - assertTrue(mode.isMemoryCache()); - - assertTrue(new Mode(Mode.ModeName.DEFAULT.name()).requiresConfigSource()); - - assertFalse(new Mode(Mode.ModeName.MEMORYCACHE.name()).requiresConfigSource()); - - Set<String> modes = new HashSet<>(); - for (Mode.ModeName modeName : Mode.ModeName.values()) { - modes.add(modeName.name().toLowerCase()); - } - - assertThat(Mode.modes(), is(modes)); - - assertFalse(Mode.validModeName("foo")); - - assertThat(mode.toString(), is(Mode.ModeName.MEMORYCACHE.name().toLowerCase())); - } - - @Test(expected = IllegalArgumentException.class) - public void failWhenInvalidMode() { - new Mode("invalid_mode"); - } - - private void assertModeName(Mode.ModeName expected, Mode actual) { - assertThat(actual.name(), is(expected.name().toLowerCase())); - } -} diff --git a/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ProxyServerTest.java b/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ProxyServerTest.java index 9d6d0ca2a39..1c64631d205 100644 --- a/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ProxyServerTest.java +++ b/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ProxyServerTest.java @@ -23,7 +23,6 @@ public class ProxyServerTest { private final MemoryCache memoryCache = new MemoryCache(); private final MockConfigSource source = new MockConfigSource(); private MockConfigSourceClient client = new MockConfigSourceClient(source, memoryCache); - private final ConfigProxyStatistics statistics = new ConfigProxyStatistics(); private ProxyServer proxy; static final RawConfig fooConfig = ConfigTester.fooConfig; @@ -42,7 +41,7 @@ public class ProxyServerTest { source.clear(); source.put(fooConfig.getKey(), createConfigWithNextConfigGeneration(fooConfig, 0)); source.put(errorConfigKey, createConfigWithNextConfigGeneration(fooConfig, ErrorCode.UNKNOWN_DEFINITION)); - proxy = ProxyServer.createTestServer(source, client, memoryCache, statistics); + proxy = ProxyServer.createTestServer(source, client, memoryCache); } @After @@ -64,27 +63,6 @@ public class ProxyServerTest { assertThat(res.getPayload().toString(), is(ConfigTester.fooPayload.toString())); assertEquals(1, memoryCache.size()); assertThat(memoryCache.get(new ConfigCacheKey(fooConfig.getKey(), fooConfig.getDefMd5())), is(res)); - - - assertEquals(1, statistics.processedRequests()); - assertEquals(0, statistics.rpcRequests()); - assertEquals(0, statistics.errors()); - assertEquals(0, statistics.delayedResponses()); - - statistics.incProcessedRequests(); - statistics.incRpcRequests(); - statistics.incErrorCount(); - statistics.delayedResponses(1); - - assertEquals(2, statistics.processedRequests()); - assertEquals(1, statistics.rpcRequests()); - assertEquals(1, statistics.errors()); - assertEquals(1, statistics.delayedResponses()); - - statistics.decDelayedResponses(); - assertEquals(0, statistics.delayedResponses()); - - assertEquals(ConfigProxyStatistics.defaultEventInterval, statistics.getEventInterval().longValue()); } /** @@ -100,6 +78,14 @@ public class ProxyServerTest { assertThat(proxy.getMode().name(), is(mode)); } + // Try setting an invalid mode + try { + proxy.setMode("invalid"); + assert (false); + } catch (IllegalArgumentException e) { + assertEquals("Unrecognized mode 'invalid' supplied", e.getMessage()); + } + // Also switch to DEFAULT mode, as that is not covered above proxy.setMode("default"); assertTrue(proxy.getMode().isDefault()); @@ -228,7 +214,6 @@ public class ProxyServerTest { @Test public void testReadingSystemProperties() { ProxyServer.Properties properties = ProxyServer.getSystemProperties(); - assertThat(properties.eventInterval, is(ConfigProxyStatistics.defaultEventInterval)); assertThat(properties.configSources.length, is(1)); assertThat(properties.configSources[0], is(ProxyServer.DEFAULT_PROXY_CONFIG_SOURCES)); } diff --git a/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClientTest.java b/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClientTest.java index 7f762955b92..35f1dd8fcd8 100644 --- a/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClientTest.java +++ b/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/RpcConfigSourceClientTest.java @@ -18,7 +18,6 @@ import static org.junit.Assert.assertEquals; public class RpcConfigSourceClientTest { private MockRpcServer rpcServer; - private ConfigProxyStatistics statistics; private DelayedResponses delayedResponses; private RpcConfigSourceClient rpcConfigSourceClient; @@ -29,10 +28,9 @@ public class RpcConfigSourceClientTest { @Before public void setup() { rpcServer = new MockRpcServer(); - statistics = new ConfigProxyStatistics(); delayedResponses = new DelayedResponses(); rpcConfigSourceClient = - new RpcConfigSourceClient(rpcServer, new MockConfigSource(), statistics, + new RpcConfigSourceClient(rpcServer, new MockConfigSource(), new MemoryCache(), ProxyServer.defaultTimingValues(), delayedResponses); } @@ -57,7 +55,6 @@ public class RpcConfigSourceClientTest { public void errorResponse() { configUpdatedSendResponse(ProxyServerTest.errorConfig); assertSentResponses(0); - assertEquals(1, statistics.errors()); } @Test diff --git a/config/pom.xml b/config/pom.xml index 40ad965a410..1be07b9b1b2 100755 --- a/config/pom.xml +++ b/config/pom.xml @@ -13,11 +13,14 @@ <packaging>container-plugin</packaging> <version>7-SNAPSHOT</version> <dependencies> + <!-- provided scope --> <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <scope>provided</scope> </dependency> + + <!-- compile scope --> <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>annotations</artifactId> @@ -58,14 +61,30 @@ <version>${project.version}</version> </dependency> <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-core</artifactId> + <groupId>com.yahoo.vespa</groupId> + <artifactId>http-utils</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </dependency> + <dependency> + <groupId>net.jpountz.lz4</groupId> + <artifactId>lz4</artifactId> + </dependency> + + <!-- test scope --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <scope>provided</scope> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-core</artifactId> + <scope>test</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> @@ -78,14 +97,6 @@ <version>${project.version}</version> <scope>test</scope> </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-core</artifactId> - </dependency> - <dependency> - <groupId>net.jpountz.lz4</groupId> - <artifactId>lz4</artifactId> - </dependency> </dependencies> <profiles> <profile> diff --git a/config/src/apps/vespa-configproxy-cmd/proxycmd.h b/config/src/apps/vespa-configproxy-cmd/proxycmd.h index 49be0fff885..ca5b5e6d119 100644 --- a/config/src/apps/vespa-configproxy-cmd/proxycmd.h +++ b/config/src/apps/vespa-configproxy-cmd/proxycmd.h @@ -4,7 +4,6 @@ #include <vespa/vespalib/stllike/string.h> #include <vector> -class FRT_Supervisor; class FRT_Target; class FRT_RPCRequest; class FRT_Values; @@ -26,7 +25,6 @@ class ProxyCmd { private: std::unique_ptr<fnet::frt::StandaloneFRT> _server; - FRT_Supervisor *_supervisor; FRT_Target *_target; FRT_RPCRequest *_req; Flags _flags; diff --git a/config/src/main/java/com/yahoo/config/subscription/CfgConfigPayloadBuilder.java b/config/src/main/java/com/yahoo/config/subscription/CfgConfigPayloadBuilder.java index 90532344a58..80c55c7b558 100644 --- a/config/src/main/java/com/yahoo/config/subscription/CfgConfigPayloadBuilder.java +++ b/config/src/main/java/com/yahoo/config/subscription/CfgConfigPayloadBuilder.java @@ -14,7 +14,6 @@ import java.util.*; * Deserializes config payload (cfg format) to a ConfigPayload. * * @author hmusum - * @since 5.1.6 */ public class CfgConfigPayloadBuilder { private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(CfgConfigPayloadBuilder.class.getName()); @@ -29,6 +28,7 @@ public class CfgConfigPayloadBuilder { return ConfigPayload.fromBuilder(deserializeToBuilder(lines)); } + @SuppressWarnings("WeakerAccess") public ConfigPayloadBuilder deserializeToBuilder(List<String> lines) { int lineNum = 1; ConfigPayloadBuilder payloadBuilder = new ConfigPayloadBuilder(); diff --git a/config/src/main/java/com/yahoo/config/subscription/ConfigGetter.java b/config/src/main/java/com/yahoo/config/subscription/ConfigGetter.java index 204d85015cb..e126b13388a 100755 --- a/config/src/main/java/com/yahoo/config/subscription/ConfigGetter.java +++ b/config/src/main/java/com/yahoo/config/subscription/ConfigGetter.java @@ -69,7 +69,7 @@ public class ConfigGetter<T extends ConfigInstance> { * @return an instance of a config class */ public static <T extends ConfigInstance> T getConfig(Class<T> c, String configId) { - ConfigGetter<T> getter = new ConfigGetter<T>(c); + ConfigGetter<T> getter = new ConfigGetter<>(c); return getter.getConfig(configId); } @@ -82,7 +82,7 @@ public class ConfigGetter<T extends ConfigInstance> { * @return an instance of a config class */ public static <T extends ConfigInstance> T getConfig(Class<T> c, String configId, ConfigSource source) { - ConfigGetter<T> getter = new ConfigGetter<T>(source, c); + ConfigGetter<T> getter = new ConfigGetter<>(source, c); return getter.getConfig(configId); } } diff --git a/config/src/main/java/com/yahoo/config/subscription/ConfigInstanceUtil.java b/config/src/main/java/com/yahoo/config/subscription/ConfigInstanceUtil.java index cce3ce0a0c5..fb2a3acbfdc 100644 --- a/config/src/main/java/com/yahoo/config/subscription/ConfigInstanceUtil.java +++ b/config/src/main/java/com/yahoo/config/subscription/ConfigInstanceUtil.java @@ -43,10 +43,10 @@ public class ConfigInstanceUtil { ConfigPayload payload) { T instance; try { - ConfigTransformer<?> transformer = new ConfigTransformer<T>(type); - ConfigBuilder instanceBuilder = transformer.toConfigBuilder(payload); + ConfigTransformer<?> transformer = new ConfigTransformer<>(type); + ConfigInstance.Builder instanceBuilder = transformer.toConfigBuilder(payload); Constructor<T> constructor = type.getConstructor(instanceBuilder.getClass()); - instance = constructor.newInstance((ConfigInstance.Builder) instanceBuilder); + instance = constructor.newInstance(instanceBuilder); // Workaround for JDK7, where compilation fails due to fields being // private and not accessible from T. Reference it as a diff --git a/config/src/main/java/com/yahoo/config/subscription/ConfigSet.java b/config/src/main/java/com/yahoo/config/subscription/ConfigSet.java index be4ab8f2116..57f4ad863f2 100644 --- a/config/src/main/java/com/yahoo/config/subscription/ConfigSet.java +++ b/config/src/main/java/com/yahoo/config/subscription/ConfigSet.java @@ -11,7 +11,6 @@ import com.yahoo.vespa.config.ConfigKey; * Config source as a programmatically built set of {@link com.yahoo.config.ConfigInstance}s * * @author vegardh - * @since 5.1 */ public class ConfigSet implements ConfigSource { private final Map<ConfigKey<?>, ConfigInstance.Builder> configs = new ConcurrentHashMap<>(); diff --git a/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSetSubscription.java b/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSetSubscription.java index dcf18597046..aa80cc75ef0 100644 --- a/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSetSubscription.java +++ b/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSetSubscription.java @@ -23,7 +23,7 @@ public class ConfigSetSubscription<T extends ConfigInstance> extends ConfigSubsc super(key, subscriber); if (!(cset instanceof ConfigSet)) throw new IllegalArgumentException("Source is not a ConfigSet: "+cset); this.set=(ConfigSet) cset; - subKey = new ConfigKey<T>(configClass, key.getConfigId()); + subKey = new ConfigKey<>(configClass, key.getConfigId()); if (!set.contains(subKey)) { throw new IllegalArgumentException("The given ConfigSet "+set+" does not contain a config for "+subKey); } diff --git a/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSubscription.java b/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSubscription.java index e0d4e6e6390..c02301a0c17 100644 --- a/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSubscription.java +++ b/config/src/main/java/com/yahoo/config/subscription/impl/ConfigSubscription.java @@ -57,7 +57,7 @@ public abstract class ConfigSubscription<T extends ConfigInstance> { this(false, 0L, false, false, null); } - private ConfigState<T> createUnchanged() { return new ConfigState<T>(generation, config); } + private ConfigState<T> createUnchanged() { return new ConfigState<>(generation, config); } public boolean isConfigChanged() { return configChanged; } public boolean isGenerationChanged() { return generationChanged; } public Long getGeneration() { return generation; } @@ -93,7 +93,7 @@ public abstract class ConfigSubscription<T extends ConfigInstance> { this.key = key; this.configClass = key.getConfigClass(); this.subscriber = subscriber; - this.config.set(new ConfigState<T>()); + this.config.set(new ConfigState<>()); } diff --git a/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigSubscription.java b/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigSubscription.java index 58f720b8cb6..d9366c28b9b 100644 --- a/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigSubscription.java +++ b/config/src/main/java/com/yahoo/config/subscription/impl/JRTConfigSubscription.java @@ -163,8 +163,9 @@ public class JRTConfigSubscription<T extends ConfigInstance> extends ConfigSubsc @SuppressWarnings("serial") public void close() { super.close(); - reqQueue = new LinkedBlockingQueue<JRTClientConfigRequest>() { - @Override public void put(JRTClientConfigRequest e) throws InterruptedException { + reqQueue = new LinkedBlockingQueue<>() { + @Override + public void put(JRTClientConfigRequest e) throws InterruptedException { // When closed, throw away all requests that callbacks try to put } }; diff --git a/config/src/main/java/com/yahoo/config/subscription/impl/JarConfigSubscription.java b/config/src/main/java/com/yahoo/config/subscription/impl/JarConfigSubscription.java index 6388b05e0cf..a96499482c5 100644 --- a/config/src/main/java/com/yahoo/config/subscription/impl/JarConfigSubscription.java +++ b/config/src/main/java/com/yahoo/config/subscription/impl/JarConfigSubscription.java @@ -4,6 +4,7 @@ package com.yahoo.config.subscription.impl; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.jar.JarFile; import java.util.zip.ZipEntry; @@ -54,7 +55,7 @@ public class JarConfigSubscription<T extends ConfigInstance> extends ConfigSubsc if (zipEntry==null) throw new IllegalArgumentException("Config '" + key.getName() + "' not found in '" + jarName + "!/" + path + "'."); T config = null; try { - ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(Arrays.asList(IOUtils.readAll(new InputStreamReader(jarFile.getInputStream(zipEntry), "UTF-8")).split("\n"))); + ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(Arrays.asList(IOUtils.readAll(new InputStreamReader(jarFile.getInputStream(zipEntry), StandardCharsets.UTF_8)).split("\n"))); config = payload.toInstance(configClass, key.getConfigId()); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigDefinition.java b/config/src/main/java/com/yahoo/vespa/config/ConfigDefinition.java index b969220ee05..0f04b4caf08 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigDefinition.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigDefinition.java @@ -1,54 +1,58 @@ // 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; -import com.yahoo.config.codegen.CNode; import com.yahoo.yolean.Exceptions; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.logging.Logger; import java.util.regex.Pattern; /** * Represents one legal def file, or (internally) one array or inner array definition in a def file. - * Definitions are comparable based on version. * @author vegardh * */ -public class ConfigDefinition implements Comparable<ConfigDefinition> { +public class ConfigDefinition { public static final Pattern namePattern = Pattern.compile("[a-zA-Z][a-zA-Z0-9-_]*"); - public static final Pattern namespacePattern = Pattern.compile("[a-zA-Z][a-zA-Z0-9-\\._]*"); + public static final Pattern namespacePattern = Pattern.compile("[a-zA-Z][a-zA-Z0-9-._]*"); public static Logger log = Logger.getLogger(ConfigDefinition.class.getName()); private final String name; private final String version; private final String namespace; - protected ConfigDefinition parent = null; + ConfigDefinition parent = null; // TODO Strings without default are null, could be not OK. - private Map<String, StringDef> stringDefs = new LinkedHashMap<String, StringDef>(); - private Map<String, BoolDef> boolDefs = new LinkedHashMap<String, BoolDef>(); - private Map<String, IntDef> intDefs = new LinkedHashMap<String, IntDef>(); - private Map<String, LongDef> longDefs = new LinkedHashMap<String, LongDef>(); - private Map<String, DoubleDef> doubleDefs = new LinkedHashMap<String, DoubleDef>(); - private Map<String, EnumDef> enumDefs = new LinkedHashMap<String, EnumDef>(); - private Map<String, RefDef> referenceDefs = new LinkedHashMap<String, RefDef>(); - private Map<String, FileDef> fileDefs = new LinkedHashMap<String, FileDef>(); + private Map<String, StringDef> stringDefs = new LinkedHashMap<>(); + private Map<String, BoolDef> boolDefs = new LinkedHashMap<>(); + private Map<String, IntDef> intDefs = new LinkedHashMap<>(); + private Map<String, LongDef> longDefs = new LinkedHashMap<>(); + private Map<String, DoubleDef> doubleDefs = new LinkedHashMap<>(); + private Map<String, EnumDef> enumDefs = new LinkedHashMap<>(); + private Map<String, RefDef> referenceDefs = new LinkedHashMap<>(); + private Map<String, FileDef> fileDefs = new LinkedHashMap<>(); private Map<String, PathDef> pathDefs = new LinkedHashMap<>(); private Map<String, UrlDef> urlDefs = new LinkedHashMap<>(); - private Map<String, StructDef> structDefs = new LinkedHashMap<String, StructDef>(); - private Map<String, InnerArrayDef> innerArrayDefs = new LinkedHashMap<String, InnerArrayDef>(); - private Map<String, ArrayDef> arrayDefs = new LinkedHashMap<String, ArrayDef>(); + private Map<String, StructDef> structDefs = new LinkedHashMap<>(); + private Map<String, InnerArrayDef> innerArrayDefs = new LinkedHashMap<>(); + private Map<String, ArrayDef> arrayDefs = new LinkedHashMap<>(); private Map<String, LeafMapDef> leafMapDefs = new LinkedHashMap<>(); private Map<String, StructMapDef> structMapDefs = new LinkedHashMap<>(); - public static final Integer INT_MIN = -0x80000000; - public static final Integer INT_MAX = 0x7fffffff; + static final Integer INT_MIN = -0x80000000; + static final Integer INT_MAX = 0x7fffffff; - public static final Long LONG_MIN = -0x8000000000000000L; - public static final Long LONG_MAX = 0x7fffffffffffffffL; + static final Long LONG_MIN = -0x8000000000000000L; + static final Long LONG_MAX = 0x7fffffffffffffffL; - public static final Double DOUBLE_MIN = -1e308d; - public static final Double DOUBLE_MAX = 1e308d; + private static final Double DOUBLE_MIN = -1e308d; + private static final Double DOUBLE_MAX = 1e308d; public ConfigDefinition(String name, String version, String namespace) { this.name = name; @@ -56,29 +60,21 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { this.namespace = namespace; } - public ConfigDefinition(String name, String version) { - this(name, version, CNode.DEFAULT_NAMESPACE); - } - public String getName() { return name; } - public String getVersion() { - return version; - } - public String getNamespace() { return namespace; } /** @return The parent ConfigDefinition, or null if this is the root. */ - public ConfigDefinition getParent() { + private ConfigDefinition getParent() { return parent; } /** @return The root ConfigDefinition, might be this. */ - public ConfigDefinition getRoot() { + private ConfigDefinition getRoot() { ConfigDefinition ancestor = this; while (ancestor.getParent() != null) { ancestor = ancestor.getParent(); @@ -205,7 +201,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { if (commaSep==null) { return null; } - List<String> in = new ArrayList<String>(); + List<String> in = new ArrayList<>(); for (String val: commaSep.split(",")) { in.add(val.trim()); } @@ -233,7 +229,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { return enumVals; } - public boolean checkValue(String id, String val, int index) { + boolean checkValue(String id, String val, int index) { if ("int".equals(getType())) { return checkInt(id, val, index); } else if ("long".equals(getType())) { @@ -342,8 +338,8 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { * of as an inner array with only one element. */ public static class StructDef extends ConfigDefinition { - public StructDef(String name, String version, ConfigDefinition parent) { - super(name, version); + StructDef(String name, String version, ConfigDefinition parent) { + super(name, version, parent.getNamespace()); this.parent = parent; } } @@ -354,8 +350,8 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { * */ public static class InnerArrayDef extends ConfigDefinition { - public InnerArrayDef(String name, String version, ConfigDefinition parent) { - super(name, version); + InnerArrayDef(String name, String version, ConfigDefinition parent) { + super(name, version, parent.getNamespace()); this.parent = parent; } } @@ -367,8 +363,8 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { */ public static class ArrayDef extends ConfigDefinition { private TypeSpec typeSpec; - public ArrayDef(String name, String version, ConfigDefinition parent) { - super(name, version); + ArrayDef(String name, String version, ConfigDefinition parent) { + super(name, version, parent.getNamespace()); this.parent = parent; } public TypeSpec getTypeSpec() { @@ -393,8 +389,8 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { */ public static class LeafMapDef extends ConfigDefinition { private TypeSpec typeSpec; - public LeafMapDef(String name, String version, ConfigDefinition parent) { - super(name, version); + LeafMapDef(String name, String version, ConfigDefinition parent) { + super(name, version, parent.getNamespace()); this.parent = parent; } public TypeSpec getTypeSpec() { @@ -411,8 +407,8 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { * */ public static class StructMapDef extends ConfigDefinition { - public StructMapDef(String name, String version, ConfigDefinition parent) { - super(name, version); + StructMapDef(String name, String version, ConfigDefinition parent) { + super(name, version, parent.getNamespace()); this.parent = parent; } } @@ -428,14 +424,14 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class EnumDef implements DefaultValued<String>{ private List<String> vals; private String defVal; - public EnumDef(List<String> vals, String defVal) { + EnumDef(List<String> vals, String defVal) { if (defVal!=null && !vals.contains(defVal)) { throw new IllegalArgumentException("Def val "+defVal+" is not in given vals "+vals); } this.vals = vals; this.defVal = defVal; } - public List<String> getVals() { + List<String> getVals() { return vals; } @@ -448,7 +444,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class StringDef implements DefaultValued<String> { private String defVal; - public StringDef(String def) { + StringDef(String def) { this.defVal=def; } @@ -461,7 +457,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class BoolDef implements DefaultValued<Boolean> { private Boolean defVal; - public BoolDef(Boolean def) { + BoolDef(Boolean def) { this.defVal=def; } @@ -478,29 +474,21 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { private Double defVal; private Double min; private Double max; - public DoubleDef(Double defVal, Double min, Double max) { + DoubleDef(Double defVal, Double min, Double max) { super(); this.defVal = defVal; - if (min == null) { - this.min = DOUBLE_MIN; - } else { - this.min = min; - } - if (max == null){ - this.max = DOUBLE_MAX; - } else { - this.max = max; - } + this.min = Objects.requireNonNullElse(min, DOUBLE_MIN); + this.max = Objects.requireNonNullElse(max, DOUBLE_MAX); } @Override public Double getDefVal() { return defVal; } - public Double getMin() { + Double getMin() { return min; } - public Double getMax() { + Double getMax() { return max; } } @@ -509,19 +497,11 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { private Integer defVal; private Integer min; private Integer max; - public IntDef(Integer def, Integer min, Integer max) { + IntDef(Integer def, Integer min, Integer max) { super(); this.defVal = def; - if (min == null) { - this.min = INT_MIN; - } else { - this.min = min; - } - if (max == null) { - this.max = INT_MAX; - } else { - this.max = max; - } + this.min = Objects.requireNonNullElse(min, INT_MIN); + this.max = Objects.requireNonNullElse(max, INT_MAX); } @Override @@ -540,19 +520,11 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { private Long defVal; private Long min; private Long max; - public LongDef(Long def, Long min, Long max) { + LongDef(Long def, Long min, Long max) { super(); this.defVal = def; - if (min == null) { - this.min = LONG_MIN; - } else { - this.min = min; - } - if (max == null) { - this.max = LONG_MAX; - } else { - this.max = max; - } + this.min = Objects.requireNonNullElse(min, LONG_MIN); + this.max = Objects.requireNonNullElse(max, LONG_MAX); } @Override @@ -570,7 +542,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class RefDef implements DefaultValued<String>{ private String defVal; - public RefDef(String defVal) { + RefDef(String defVal) { super(); this.defVal = defVal; } @@ -584,7 +556,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class FileDef implements DefaultValued<String>{ private String defVal; - public FileDef(String defVal) { + FileDef(String defVal) { super(); this.defVal = defVal; } @@ -598,7 +570,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class PathDef implements DefaultValued<String>{ private String defVal; - public PathDef(String defVal) { + PathDef(String defVal) { this.defVal = defVal; } @@ -611,7 +583,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { public static class UrlDef implements DefaultValued<String>{ private String defVal; - public UrlDef(String defVal) { + UrlDef(String defVal) { this.defVal = defVal; } @@ -634,7 +606,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { } public void addEnumDef(String id, List<String> vals, String defVal) { - List<String> in = new ArrayList<String>(); + List<String> in = new ArrayList<>(); for (String ins: vals) { in.add(ins.trim()); } @@ -940,15 +912,15 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { return true; } - static void failTooSmall(Object val, Object min, String defName, String valKey) { + private static void failTooSmall(Object val, Object min, String defName, String valKey) { defFail("Value \""+valKey+"\" outside range in definition \""+defName+"\": "+val+"<"+min); } - static void failTooBig(Object val, Object max, String defName, String valKey) { + private static void failTooBig(Object val, Object max, String defName, String valKey) { defFail("Value \""+valKey+"\" outside range in definition \""+defName+"\": "+val+">"+max); } - static void failInvalidEnum(Object val, String defName, String defKey) { + private static void failInvalidEnum(Object val, String defName, String defKey) { defFail("Invalid enum value \""+val+"\" for \""+defKey+"\" in definition \""+defName); } @@ -957,7 +929,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { * @param msg failure message * @return warnings list with msg added */ - static List<String> defFail(String msg) { + private static List<String> defFail(String msg) { throw new IllegalArgumentException(msg); } @@ -1069,7 +1041,7 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { * * @return a string composed of the ancestors of this ConfigDefinition, not including the root. */ - public String getAncestorString() { + private String getAncestorString() { StringBuilder ret = new StringBuilder(); ConfigDefinition ancestor = this; while (ancestor.getParent() != null) { @@ -1080,15 +1052,6 @@ public class ConfigDefinition implements Comparable<ConfigDefinition> { } @Override - public int compareTo(ConfigDefinition other) { - Objects.requireNonNull(other); - if (!getName().equals(other.getName())) { - throw new IllegalArgumentException("Different def names used to compare: "+getName()+"/"+other.getName()); - } - return new VersionComparator().compare(getVersion(),other.getVersion()); - } - - @Override public String toString() { return getNamespace() + "." + getName(); } diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionBuilder.java b/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionBuilder.java index 0121e47b9ae..b265b13a6b6 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionBuilder.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionBuilder.java @@ -112,7 +112,7 @@ public class ConfigDefinitionBuilder { } - static void addNode(ConfigDefinition def, LeafCNode.IntegerLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.IntegerLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addIntDef(leaf.getName(), Integer.valueOf(leaf.getDefaultValue().getValue())); } else { @@ -120,7 +120,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.LongLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.LongLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addLongDef(leaf.getName(), Long.valueOf(leaf.getDefaultValue().getValue())); } else { @@ -128,7 +128,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.BooleanLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.BooleanLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addBoolDef(leaf.getName(), Boolean.valueOf(leaf.getDefaultValue().getValue())); } else { @@ -136,7 +136,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.DoubleLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.DoubleLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addDoubleDef(leaf.getName(), Double.valueOf(leaf.getDefaultValue().getValue())); } else { @@ -144,7 +144,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.StringLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.StringLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addStringDef(leaf.getName(), leaf.getDefaultValue().getValue()); } else { @@ -152,7 +152,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.ReferenceLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.ReferenceLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addReferenceDef(leaf.getName(), leaf.getDefaultValue().getValue()); } else { @@ -160,7 +160,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.FileLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.FileLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addFileDef(leaf.getName(), leaf.getDefaultValue().getValue()); } else { @@ -168,7 +168,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.PathLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.PathLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addPathDef(leaf.getName(), leaf.getDefaultValue().getValue()); } else { @@ -176,7 +176,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.UrlLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.UrlLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addUrlDef(leaf.getName(), leaf.getDefaultValue().getValue()); } else { @@ -184,7 +184,7 @@ public class ConfigDefinitionBuilder { } } - static void addNode(ConfigDefinition def, LeafCNode.EnumLeaf leaf) { + private static void addNode(ConfigDefinition def, LeafCNode.EnumLeaf leaf) { if (leaf.getDefaultValue() != null) { def.addEnumDef(leaf.getName(), Arrays.asList(leaf.getLegalValues()), leaf.getDefaultValue().getValue()); } else { @@ -192,7 +192,7 @@ public class ConfigDefinitionBuilder { } } - static String convertToEnumValueCommaSeparated(String[] enumValues) { + private static String convertToEnumValueCommaSeparated(String[] enumValues) { StringBuilder sb = new StringBuilder(); for (String s : enumValues) { sb.append(s); diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionSet.java b/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionSet.java deleted file mode 100644 index c174ead815f..00000000000 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigDefinitionSet.java +++ /dev/null @@ -1,64 +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; - -import com.yahoo.config.codegen.CNode; -import com.yahoo.log.LogLevel; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Class to hold config definitions and resolving requests for the correct definition - * - * @author hmusum - * @since 5.1 - */ -public class ConfigDefinitionSet { - private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(ConfigDefinitionSet.class.getName()); - - private final Map<ConfigDefinitionKey, ConfigDefinition> defs = new ConcurrentHashMap<ConfigDefinitionKey, ConfigDefinition>(); - - public ConfigDefinitionSet() { - - } - - public void add(ConfigDefinitionKey key, ConfigDefinition def) { - log.log(LogLevel.DEBUG, "Adding to set: " + key); - defs.put(key, def); - } - - /** - * Returns a ConfigDefinition from the set matching the given <code>key</code>. If no ConfigDefinition - * is found in the set, it will try to find a ConfigDefinition with same name in the default namespace. - * @param key a {@link ConfigDefinitionKey} - * @return a ConfigDefinition if found, else null - */ - public ConfigDefinition get(ConfigDefinitionKey key) { - log.log(LogLevel.DEBUG, "Getting from set " + defs + " for key " + key); - ConfigDefinition ret = defs.get(key); - if (ret == null) { - // Return entry if we fallback to default namespace - log.log(LogLevel.DEBUG, "Found no def for key " + key + ", trying to find def with same name in default namespace"); - for (Map.Entry<ConfigDefinitionKey, ConfigDefinition> entry : defs.entrySet()) { - if (key.getName().equals(entry.getKey().getName()) && entry.getKey().getNamespace().equals(CNode.DEFAULT_NAMESPACE)) { - return entry.getValue(); - } - } - } - return ret; - } - - public int size() { - return defs.size(); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (ConfigDefinitionKey key : defs.keySet()) { - sb.append(key.toString()).append("\n"); - } - return sb.toString(); - } - -} diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java b/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java index c478eaa18d3..1f955a7501f 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java @@ -184,7 +184,7 @@ public class ConfigFileFormat implements SlimeFormat, ObjectTraverser { encode(os, slime.get()); } - public void encode(OutputStream os, Inspector inspector) throws IOException { + private void encode(OutputStream os, Inspector inspector) throws IOException { this.out = new DataOutputStream(os); this.nodeStack = new Stack<>(); nodeStack.push(new Node(root)); @@ -226,7 +226,7 @@ public class ConfigFileFormat implements SlimeFormat, ObjectTraverser { this.mapKey = mapKey; } - public Node(CNode node) { + Node(CNode node) { this(node, -1, ""); } } diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigHelper.java b/config/src/main/java/com/yahoo/vespa/config/ConfigHelper.java deleted file mode 100644 index 0f3b4b76aa8..00000000000 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigHelper.java +++ /dev/null @@ -1,51 +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; - -import com.yahoo.config.subscription.ConfigSourceSet; - -/** - * Helper class for config applications (currently ConfigManager and ConfigProxy). - * - * @author <a href="gv@yahoo-inc.com">G. Voldengen</a> - */ -public class ConfigHelper { - private final JRTConnectionPool jrtConnectionPool; - private final TimingValues timingValues; - - /** - * @param configSourceSet The set of config sources for this helper. - */ - public ConfigHelper(ConfigSourceSet configSourceSet) { - this(configSourceSet, new TimingValues()); - } - - /** - * @param configSourceSet The set of config sources for this helper. - * @param timingValues values for timeouts and delays, see {@link TimingValues} - */ - public ConfigHelper(ConfigSourceSet configSourceSet, TimingValues timingValues) { - jrtConnectionPool = new JRTConnectionPool(configSourceSet); - this.timingValues = timingValues; - } - - /** - * @return the config sources (remote servers and/or proxies) in this helper's connection pool. - */ - public ConfigSourceSet getConfigSourceSet() { - return jrtConnectionPool.getSourceSet(); - } - - /** - * @return the connection pool for this config helper. - */ - public JRTConnectionPool getConnectionPool() { - return jrtConnectionPool; - } - - /** - * @return the timing values for this config helper. - */ - public TimingValues getTimingValues() { - return timingValues; - } -} diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java index 5655ff596f1..65106f158fc 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java @@ -433,7 +433,7 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> { if (structBuilderClass == null) throw new RuntimeException("Could not find builder class " + structBuilderName); try { - return structBuilderClass.getDeclaredConstructor(new Class<?>[]{}); + return structBuilderClass.getDeclaredConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException("Could not create class '" + "'" + structBuilderClass.getName() + "'"); } diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigVerification.java b/config/src/main/java/com/yahoo/vespa/config/ConfigVerification.java index a020c8c8c55..0888004ef02 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigVerification.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigVerification.java @@ -1,18 +1,23 @@ // 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; +import ai.vespa.util.http.VespaHttpClientBuilder; import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Inspector; import com.yahoo.slime.JsonDecoder; import com.yahoo.slime.Slime; import com.yahoo.text.Utf8; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; /** * Tool to verify that configs across multiple config servers are the same. @@ -33,11 +38,13 @@ public class ConfigVerification { for (String arg : args) { configservers.add(prefix + arg + ":" + port + "/config/v2/tenant/" + tenant + "/application/" + appName + "/environment/" + environment + "/region/" + region + "/instance/" + instance + "/?recursive=true"); } - System.exit(compareConfigs(listConfigs(configservers))); + try (CloseableHttpClient httpClient = VespaHttpClientBuilder.createWithBasicConnectionManager().build()) { + System.exit(compareConfigs(listConfigs(configservers, httpClient), httpClient)); + } } - private static Map<String, Stack<String>> listConfigs(List<String> urls) throws IOException { - Map<String, String> outputs = performRequests(urls); + private static Map<String, Stack<String>> listConfigs(List<String> urls, CloseableHttpClient httpClient) throws IOException { + Map<String, String> outputs = performRequests(urls, httpClient); Map<String, Stack<String>> recurseMappings = new LinkedHashMap<>(); for (Map.Entry<String, String> entry : outputs.entrySet()) { @@ -57,21 +64,21 @@ public class ConfigVerification { return recurseMappings; } - private static Map<String, String> performRequests(List<String> urls) throws IOException { + private static Map<String, String> performRequests(List<String> urls, CloseableHttpClient httpClient) throws IOException { Map<String, String> outputs = new LinkedHashMap<>(); for (String url : urls) { - outputs.put(url, performRequest(url)); + outputs.put(url, performRequest(url, httpClient)); } return outputs; } - private static int compareConfigs(Map<String, Stack<String>> mappings) throws IOException { + private static int compareConfigs(Map<String, Stack<String>> mappings, CloseableHttpClient httpClient) throws IOException { for (int n = 0; n < mappings.values().iterator().next().size(); n++) { List<String> recurseUrls = new ArrayList<>(); for (Map.Entry<String, Stack<String>> entry : mappings.entrySet()) { recurseUrls.add(entry.getValue().pop()); } - int ret = compareOutputs(performRequests(recurseUrls)); + int ret = compareOutputs(performRequests(recurseUrls, httpClient)); if (ret != 0) { return ret; } @@ -90,14 +97,7 @@ public class ConfigVerification { return 0; } - private static String performRequest(String url) throws IOException { - URLConnection connection = new URL(url).openConnection(); - InputStream response = connection.getInputStream(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int ch; - while ((ch = response.read()) > -1) { - baos.write(ch); - } - return Utf8.toString(baos.toByteArray()); + private static String performRequest(String url, CloseableHttpClient httpClient) throws IOException { + return httpClient.execute(new HttpGet(url), new BasicResponseHandler()); } } diff --git a/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java b/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java index 8e8c767e393..2fbbda2aa38 100644 --- a/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java +++ b/config/src/main/java/com/yahoo/vespa/config/GenericConfig.java @@ -11,19 +11,21 @@ import com.yahoo.config.ConfigInstance; * when we don't have the schema. * * @author Ulf Lilleengen - * @since 5.1 */ public class GenericConfig { public static class GenericConfigBuilder implements ConfigInstance.Builder { + private final ConfigPayloadBuilder payloadBuilder; private final ConfigDefinitionKey defKey; + public GenericConfigBuilder(ConfigDefinitionKey defKey, ConfigPayloadBuilder payloadBuilder) { this.defKey = defKey; this.payloadBuilder = payloadBuilder; } + + @SuppressWarnings("unused") // Called by reflection private ConfigBuilder override(GenericConfigBuilder superior) { - ConfigPayloadBuilder superiorPayload = superior.payloadBuilder; - payloadBuilder.override(superiorPayload); + payloadBuilder.override(superior.payloadBuilder); return this; } @@ -49,4 +51,5 @@ public class GenericConfig { return ""; } } + } diff --git a/config/src/main/java/com/yahoo/vespa/config/JRTConnection.java b/config/src/main/java/com/yahoo/vespa/config/JRTConnection.java index 01da823b87b..cde46eb9de0 100644 --- a/config/src/main/java/com/yahoo/vespa/config/JRTConnection.java +++ b/config/src/main/java/com/yahoo/vespa/config/JRTConnection.java @@ -10,7 +10,7 @@ import java.util.logging.Logger; /** * A JRT connection to a config server or config proxy. * - * @author <a href="mailto:gunnarga@yahoo-inc.com">Gunnar Gauslaa Bergem</a> + * @author Gunnar Gauslaa Bergem */ public class JRTConnection implements Connection { public final static Logger logger = Logger.getLogger(JRTConnection.class.getPackage().getName()); @@ -78,10 +78,6 @@ public class JRTConnection implements Connection { lastSuccess = System.currentTimeMillis(); } - public void setLastSuccess() { - lastSuccess = System.currentTimeMillis(); - } - public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Address: "); diff --git a/config/src/main/java/com/yahoo/vespa/config/TimingValues.java b/config/src/main/java/com/yahoo/vespa/config/TimingValues.java index 6a814f99ee3..113d561bf87 100644 --- a/config/src/main/java/com/yahoo/vespa/config/TimingValues.java +++ b/config/src/main/java/com/yahoo/vespa/config/TimingValues.java @@ -6,7 +6,7 @@ import java.util.Random; /** * Timeouts, delays and retries used in RPC config protocol. * - * @author <a href="mailto:gunnarga@yahoo-inc.com">Gunnar Gauslaa Bergem</a> + * @author Gunnar Gauslaa Bergem */ public class TimingValues { public static final long defaultNextConfigTimeout = 1000; diff --git a/config/src/main/java/com/yahoo/vespa/config/benchmark/StressTester.java b/config/src/main/java/com/yahoo/vespa/config/benchmark/StressTester.java index 4234a6deff1..0152c0bc3ff 100644 --- a/config/src/main/java/com/yahoo/vespa/config/benchmark/StressTester.java +++ b/config/src/main/java/com/yahoo/vespa/config/benchmark/StressTester.java @@ -14,7 +14,6 @@ import java.util.*; * with test classes that implement the {@link Tester} interface. * * @author hmusum - * @since 5.1.5 */ public class StressTester { private static boolean debug = false; diff --git a/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigCompiler.java b/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigCompiler.java index 7565d198a7a..e4cedfafeea 100644 --- a/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigCompiler.java +++ b/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigCompiler.java @@ -5,7 +5,6 @@ package com.yahoo.vespa.config.buildergen; * Interface towards compilers for compiling builders from a config class definition. * * @author Ulf Lilleengen - * @since 5.2 */ public interface ConfigCompiler { CompiledBuilder compile(ConfigDefinitionClass defClass); diff --git a/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigDefinitionClass.java b/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigDefinitionClass.java index 6c3f3f90f40..8f222de3b55 100644 --- a/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigDefinitionClass.java +++ b/config/src/main/java/com/yahoo/vespa/config/buildergen/ConfigDefinitionClass.java @@ -4,7 +4,8 @@ package com.yahoo.vespa.config.buildergen; /** * @author Ulf Lilleengen */ -public class ConfigDefinitionClass { +class ConfigDefinitionClass { + private final String name; private final String pkg; private final String definition; @@ -19,11 +20,8 @@ public class ConfigDefinitionClass { return definition; } - String getSimpleName() { - return name; - } - String getName() { return pkg + "." + name; } + } diff --git a/config/src/main/java/com/yahoo/vespa/config/buildergen/LazyConfigCompiler.java b/config/src/main/java/com/yahoo/vespa/config/buildergen/LazyConfigCompiler.java index 3d8e2d1c2b3..2fc3cd7aa19 100644 --- a/config/src/main/java/com/yahoo/vespa/config/buildergen/LazyConfigCompiler.java +++ b/config/src/main/java/com/yahoo/vespa/config/buildergen/LazyConfigCompiler.java @@ -5,7 +5,6 @@ import com.yahoo.config.ConfigInstance; import javax.tools.*; import java.io.File; -import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -76,7 +75,7 @@ public class LazyConfigCompiler implements ConfigCompiler { @SuppressWarnings("unchecked") private <BUILDER extends ConfigInstance.Builder> BUILDER loadBuilder(String builderClassUrl) { try { - Class<BUILDER> clazz = (Class<BUILDER>) classLoader.<BUILDER>loadClass(builderClassUrl); + Class<BUILDER> clazz = (Class<BUILDER>) classLoader.loadClass(builderClassUrl); return clazz.getDeclaredConstructor().newInstance(); } catch (ReflectiveOperationException e) { throw new RuntimeException("Error creating new instance of '" + builderClassUrl + "'", e); diff --git a/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeConfigResponse.java b/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeConfigResponse.java index 566e3597269..327acab53d3 100644 --- a/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeConfigResponse.java +++ b/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeConfigResponse.java @@ -9,6 +9,7 @@ import com.yahoo.vespa.config.ConfigPayload; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -60,7 +61,7 @@ public class SlimeConfigResponse implements ConfigResponse { Payload v1payload = Payload.from(payload, compressionInfo).withCompression(CompressionType.UNCOMPRESSED); try { ConfigPayload.fromUtf8Array(v1payload.getData()).serialize(baos, format); - return Arrays.asList(baos.toString("UTF-8").split("\\n")); + return Arrays.asList(baos.toString(StandardCharsets.UTF_8).split("\\n")); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeResponseData.java b/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeResponseData.java index 6add29074d1..40414c24c96 100644 --- a/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeResponseData.java +++ b/config/src/main/java/com/yahoo/vespa/config/protocol/SlimeResponseData.java @@ -70,7 +70,7 @@ class SlimeResponseData { boolean getResponseInternalRedeployment() { Inspector inspector = getResponseField(RESPONSE_INTERNAL_REDEPLOY); - return inspector.valid() ? inspector.asBool() : false; + return inspector.valid() && inspector.asBool(); } } diff --git a/config/src/main/java/com/yahoo/vespa/config/protocol/Utf8SerializedString.java b/config/src/main/java/com/yahoo/vespa/config/protocol/Utf8SerializedString.java deleted file mode 100644 index 43c0f7f41e7..00000000000 --- a/config/src/main/java/com/yahoo/vespa/config/protocol/Utf8SerializedString.java +++ /dev/null @@ -1,87 +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.protocol; - -import com.fasterxml.jackson.core.SerializableString; -import com.yahoo.text.Utf8Array; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Wraps utf8array as a {@link com.fasterxml.jackson.core.SerializableString} to avoid extra copy. - * - * @author Ulf Lilleengen - * @since 5.17 - */ -public class Utf8SerializedString implements SerializableString { - private final Utf8Array value; - public Utf8SerializedString(Utf8Array value) { - this.value = value; - } - - @Override - public String getValue() { - return value.toString(); - } - - @Override - public int charLength() { - return value.getByteLength(); - } - - @Override - public char[] asQuotedChars() { - throw new UnsupportedOperationException(); - } - - @Override - public byte[] asUnquotedUTF8() { - return value.getBytes(); - } - - @Override - public byte[] asQuotedUTF8() { - throw new UnsupportedOperationException(); - } - - @Override - public int appendQuotedUTF8(byte[] buffer, int offset) { - throw new UnsupportedOperationException(); - } - - @Override - public int appendQuoted(char[] buffer, int offset) { - throw new UnsupportedOperationException(); - } - - @Override - public int appendUnquotedUTF8(byte[] buffer, int offset) { - throw new UnsupportedOperationException(); - } - - @Override - public int appendUnquoted(char[] buffer, int offset) { - throw new UnsupportedOperationException(); - } - - @Override - public int writeQuotedUTF8(OutputStream out) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public int writeUnquotedUTF8(OutputStream out) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public int putQuotedUTF8(ByteBuffer buffer) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public int putUnquotedUTF8(ByteBuffer out) throws IOException { - throw new UnsupportedOperationException(); - } -} diff --git a/config/src/main/java/com/yahoo/vespa/config/util/ConfigUtils.java b/config/src/main/java/com/yahoo/vespa/config/util/ConfigUtils.java index bd7aae3051a..56ede8897ed 100644 --- a/config/src/main/java/com/yahoo/vespa/config/util/ConfigUtils.java +++ b/config/src/main/java/com/yahoo/vespa/config/util/ConfigUtils.java @@ -9,33 +9,42 @@ import com.yahoo.net.HostName; import com.yahoo.slime.JsonFormat; import com.yahoo.text.Utf8; import com.yahoo.text.Utf8Array; -import com.yahoo.vespa.config.*; - -import java.io.*; +import com.yahoo.vespa.config.ConfigDefinitionKey; +import com.yahoo.vespa.config.ConfigPayload; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Utilities for mangling config text, finding md5sums, version numbers in .def files etc. + * Utilities for mangling config text, finding md5sums, finding name and namespace in .def files etc. */ public class ConfigUtils { /* Patterns used for finding ranges in config definitions */ private static final Pattern intPattern = Pattern.compile(".*int.*range.*"); private static final Pattern doublePattern = Pattern.compile(".*double.*range.*"); private static final Pattern spaceBeforeCommaPatter = Pattern.compile("\\s,"); - public static final String intFormattedMax = new DecimalFormat("#.#").format(0x7fffffff); - public static final String intFormattedMin = new DecimalFormat("#.#", new DecimalFormatSymbols(Locale.ENGLISH)).format(-0x80000000); - public static final String doubleFormattedMax = new DecimalFormat("#.#").format(1e308); - public static final String doubleFormattedMin = new DecimalFormat("#.#", new DecimalFormatSymbols(Locale.ENGLISH)).format(-1e308); + private static final String intFormattedMax = new DecimalFormat("#.#").format(0x7fffffff); + private static final String intFormattedMin = new DecimalFormat("#.#", new DecimalFormatSymbols(Locale.ENGLISH)).format(-0x80000000); + private static final String doubleFormattedMax = new DecimalFormat("#.#").format(1e308); + private static final String doubleFormattedMin = new DecimalFormat("#.#", new DecimalFormatSymbols(Locale.ENGLISH)).format(-1e308); /** - * Computes Md5 hash of a list of strings. The only change to input lines before - * computing md5 is to skip empty lines. + * Computes Md5 hash of a list of strings. * * @param payload a config payload * @return the Md5 hash of the list, with lowercase letters @@ -47,30 +56,7 @@ public class ConfigUtils { } catch (IOException e) { throw new RuntimeException(e); } - MessageDigest md5 = getMd5Instance(); - md5.update(baos.toByteArray()); - return HexDump.toHexString(md5.digest()).toLowerCase(); - } - - /** - * Computes Md5 hash of a list of strings. The only change to input lines before - * computing md5 is to skip empty lines. - * - * @param lines A list of lines - * @return the Md5 hash of the list, with lowercase letters - */ - public static String getMd5(List<String> lines) { - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - // Remove empty lines - line = line.trim(); - if (line.length() > 0) { - sb.append(line).append("\n"); - } - } - MessageDigest md5 = getMd5Instance(); - md5.update(Utf8.toBytes(sb.toString())); - return HexDump.toHexString(md5.digest()).toLowerCase(); + return getMd5(baos.toByteArray()); } /** @@ -80,14 +66,16 @@ public class ConfigUtils { * @return the Md5 hash of the input, with lowercase letters */ public static String getMd5(String input) { - MessageDigest md5 = getMd5Instance(); - md5.update(IOUtils.utf8ByteBuffer(input)); - return HexDump.toHexString(md5.digest()).toLowerCase(); + return getMd5(input.getBytes(StandardCharsets.UTF_8)); } public static String getMd5(Utf8Array input) { + return getMd5(input.getBytes()); + } + + public static String getMd5(byte[] input) { MessageDigest md5 = getMd5Instance(); - md5.update(input.getBytes()); + md5.update(input); return HexDump.toHexString(md5.digest()).toLowerCase(); } @@ -95,7 +83,7 @@ public class ConfigUtils { try { return MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { - return null; + throw new RuntimeException("Could not get md5 instance"); } } @@ -105,7 +93,7 @@ public class ConfigUtils { * @return String with spaces stripped */ public static String stripSpaces(String str) { - StringBuilder ret = new StringBuilder(""); + StringBuilder ret = new StringBuilder(); boolean inQuotes = false; boolean inSpaceSequence = false; for (char c : str.toCharArray()) { @@ -161,12 +149,6 @@ public class ConfigUtils { } } - MessageDigest md5; - try { - md5 = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - return null; - } StringBuilder sb = new StringBuilder(); for (String line : linesCopy) { // Normalize line, like it's done in make-config-preproc.pl @@ -193,19 +175,7 @@ public class ConfigUtils { sb.append(line).append("\n"); } } - md5.update(Utf8.toBytes(sb.toString())); - return HexDump.toHexString(md5.digest()).toLowerCase(); - } - - /** - * Finds the def version from a reader for a def-file. Returns "" (empty string) - * if no version was found. - * - * @param in A reader to a def-file - * @return version of the def-file, or "" (empty string) if no version was found - */ - public static String getDefVersion(Reader in) { - return getDefKeyword(in, "version"); + return getMd5(sb.toString()); } /** @@ -223,17 +193,6 @@ public class ConfigUtils { return getDefKeyword(defLines, "namespace"); } - /** - * Finds the value of the keyword in <code>keyword</code> from a reader for a def-file. - * Returns "" (empty string) if no value for keyword was found. - * - * @param in A reader to a def-file - * @return value of keyword, or "" (empty string) if no line matching keyword was found - */ - public static String getDefKeyword(Reader in, String keyword) { - return getDefKeyword(getDefLines(in), keyword); - } - private static String getDefKeyword(List<String> defLines, String keyword) { for (String line : defLines) { if (line.startsWith(keyword)) { @@ -271,16 +230,15 @@ public class ConfigUtils { } /** - * Finds the name and version from a string with "name,version". - * If no name is given, the first part of the tuple will be the empty string - * If no version is given, the second part of the tuple will be the empty string + * Finds the name and namespace part from a string "name.namespace,version", which + * is how it is serialized in zookeeper (versions is always empty) * - * @param nameCommaVersion A string consisting of "name,version" - * @return a Tuple2 with first item being name and second item being version + * @param nameCommaVersion A string consisting of "name.namespace,version" or "name.namespace," + * @return a string with name.namespace */ - public static Tuple2<String, String> getNameAndVersionFromString(String nameCommaVersion) { + private static String getNameFromSerializedString(String nameCommaVersion) { String[] av = nameCommaVersion.split(","); - return new Tuple2<>(av[0], av.length >= 2 ? av[1] : ""); + return av[0]; } /** @@ -302,72 +260,22 @@ public class ConfigUtils { } /** - * Creates a ConfigDefinitionKey based on a string with namespace, name and version - * (e.g. Vespa's own config definitions in $VESPA_HOME/share/vespa/configdefinitions) - * - * @param input A string consisting of "namespace.name.version" - * @return a ConfigDefinitionKey - */ - @SuppressWarnings("deprecation") - public static ConfigDefinitionKey getConfigDefinitionKeyFromString(String input) { - final String name; - final String namespace; - if (!input.contains(".")) { - name = input; - namespace = ""; - } else if (input.lastIndexOf(".") == input.indexOf(".")) { - Tuple2<String, String> tuple = ConfigUtils.getNameAndNamespaceFromString(input); - boolean containsVersion = false; - for (int i=0; i < tuple.first.length(); i++) { - if (Character.isDigit(tuple.first.charAt(i))) { - containsVersion = true; - break; - } - } - if (containsVersion) { - name = tuple.second; - namespace = ""; - } else { - name = tuple.first; - namespace = tuple.second; - } - } else { - Tuple2<String, String> tuple = ConfigUtils.getNameAndNamespaceFromString(input); - - String tempName = tuple.second; - tuple = ConfigUtils.getNameAndNamespaceFromString(tempName); - name = tuple.first; - namespace = tuple.second; - } - return new ConfigDefinitionKey(name, namespace); - } - - /** * Creates a ConfigDefinitionKey from a string for the name of a node in ZooKeeper * that holds a config definition * * @param nodeName name of a node in ZooKeeper that holds a config definition * @return a ConfigDefinitionKey */ - @SuppressWarnings("deprecation") public static ConfigDefinitionKey createConfigDefinitionKeyFromZKString(String nodeName) { final String name; final String namespace; - if (nodeName.contains(".")) { - Tuple2<String, String> tuple = ConfigUtils.getNameAndVersionFromString(nodeName); - String tempName = tuple.first; // includes namespace - tuple = ConfigUtils.getNameAndNamespaceFromString(tempName); - name = tuple.first; - namespace = tuple.second; - } else { - Tuple2<String, String> tuple = ConfigUtils.getNameAndVersionFromString(nodeName); - name = tuple.first; - namespace = ""; - } + String tempName = ConfigUtils.getNameFromSerializedString(nodeName); // includes namespace + Tuple2<String, String> tuple = ConfigUtils.getNameAndNamespaceFromString(tempName); + name = tuple.first; + namespace = tuple.second; return new ConfigDefinitionKey(name, namespace); } - /** * Creates a ConfigDefinitionKey from a file by reading the file and parsing * contents for namespace. Name and from filename, but the filename may be prefixed @@ -392,8 +300,7 @@ public class ConfigUtils { * @param content content of a config definition * @return a ConfigDefinitionKey */ - @SuppressWarnings("deprecation") - public static ConfigDefinitionKey createConfigDefinitionKeyFromDefContent(String name, byte[] content) { + static ConfigDefinitionKey createConfigDefinitionKeyFromDefContent(String name, byte[] content) { String namespace = ConfigUtils.getDefNamespace(new StringReader(Utf8.toString(content))); if (namespace.isEmpty()) { namespace = CNode.DEFAULT_NAMESPACE; @@ -401,7 +308,6 @@ public class ConfigUtils { return new ConfigDefinitionKey(name, namespace); } - /** * Escapes a config value according to the cfg format. * @param input the string to escape diff --git a/config/src/test/java/com/yahoo/config/subscription/AppService.java b/config/src/test/java/com/yahoo/config/subscription/AppService.java index 6c395b12129..0f8d93e6fe0 100644 --- a/config/src/test/java/com/yahoo/config/subscription/AppService.java +++ b/config/src/test/java/com/yahoo/config/subscription/AppService.java @@ -10,25 +10,22 @@ import com.yahoo.vespa.config.TimingValues; * Application that subscribes to config defined in app.def and * generated code in AppConfig.java. */ -public class AppService { - protected int timesConfigured = 0; +class AppService { + private int timesConfigured = 0; - protected AppConfig config = null; + private AppConfig config = null; private final ConfigSubscriber subscriber; - protected final String configId; - final Thread configThread; - boolean stopThread = false; + private boolean stopThread = false; - public AppService(String configId, ConfigSourceSet csource) { + AppService(String configId, ConfigSourceSet csource) { this(configId, csource, null); } - public int timesConfigured() { return timesConfigured; } + int timesConfigured() { return timesConfigured; } - public AppService(String configId, ConfigSourceSet csource, TimingValues timingValues) { + private AppService(String configId, ConfigSourceSet csource, TimingValues timingValues) { if (csource == null) throw new IllegalArgumentException("Config source cannot be null"); - this.configId = configId; subscriber = new ConfigSubscriber(csource); ConfigHandle<AppConfig> temp; if (timingValues == null) { @@ -37,15 +34,12 @@ public class AppService { temp = subscriber.subscribe(AppConfig.class, configId, csource, timingValues); } final ConfigHandle<AppConfig> handle = temp; - configThread = new Thread(new Runnable() { - @Override - public void run() { - while (!stopThread) { - boolean changed = subscriber.nextConfig(500); - if (changed) { - configure(handle.getConfig()); - timesConfigured++; - } + Thread configThread = new Thread(() -> { + while (!stopThread) { + boolean changed = subscriber.nextConfig(500); + if (changed) { + configure(handle.getConfig()); + timesConfigured++; } } }); @@ -56,7 +50,7 @@ public class AppService { configThread.start(); } - public void configure(AppConfig config) { + private void configure(AppConfig config) { this.config = config; } diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java index 731a5f50816..07eeb78f0d9 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java @@ -37,6 +37,8 @@ public class ConfigGetterTest { AppConfig serviceConfig = service.getConfig(); assertTrue(service.isConfigured()); assertThat(config, is(serviceConfig)); + + service.cancelSubscription(); } @Test @@ -107,5 +109,7 @@ public class ConfigGetterTest { AppConfig serviceConfig = service.getConfig(); assertTrue(service.isConfigured()); assertThat(config, is(serviceConfig)); + + service.cancelSubscription(); } } diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java index 0c8af47dccb..ee8682efe3c 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java @@ -25,7 +25,7 @@ import static org.junit.Assert.fail; */ public class ConfigInstanceSerializerTest { @Test - public void test_that_leaf_types_are_serialized_to_json_types() throws IOException { + public void test_that_leaf_types_are_serialized_to_json_types() { SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder(); builder.boolval(false); builder.stringval("foo"); @@ -50,7 +50,7 @@ public class ConfigInstanceSerializerTest { } @Test - public void test_that_nested_structs_are_formatted_to_json() throws IOException { + public void test_that_nested_structs_are_formatted_to_json() { StructtypesConfig.Builder builder = new StructtypesConfig.Builder(); StructtypesConfig.Nested.Builder nestedBuilder = new StructtypesConfig.Nested.Builder(); StructtypesConfig.Nested.Inner.Builder innerBuilder = new StructtypesConfig.Nested.Inner.Builder(); @@ -106,7 +106,7 @@ public class ConfigInstanceSerializerTest { } @Test - public void test_that_arrays_are_formatted_to_json() throws IOException { + public void test_that_arrays_are_formatted_to_json() { ArraytypesConfig.Builder builder = new ArraytypesConfig.Builder(); builder.boolarr(true); builder.boolarr(false); @@ -149,7 +149,7 @@ public class ConfigInstanceSerializerTest { } @Test - public void test_that_maps_are_formatted_to_json() throws IOException { + public void test_that_maps_are_formatted_to_json() { MaptypesConfig.Builder builder = new MaptypesConfig.Builder(); builder.boolmap("foo", true); builder.intmap("bar", 3); @@ -195,7 +195,7 @@ public class ConfigInstanceSerializerTest { } @Test - public void test_that_non_standard_types_are_formatted_as_json_strings() throws IOException { + public void test_that_non_standard_types_are_formatted_as_json_strings() { SpecialtypesConfig.Builder builder = new SpecialtypesConfig.Builder(); builder.myfile("thefilename"); builder.myref("thereference"); diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java index e086202eca8..1da53e4c3b9 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java @@ -34,6 +34,9 @@ public class ConfigInstanceTest { assertEquals(1, service1.timesConfigured()); assertEquals(1, service2.timesConfigured()); + + service1.cancelSubscription(); + service2.cancelSubscription(); } /** @@ -107,11 +110,10 @@ public class ConfigInstanceTest { } private class TestNonstring { - private final ConfigSubscriber subscriber; - private final ConfigHandle<TestNonstringConfig> handle; + public TestNonstring(String configId) { - subscriber = new ConfigSubscriber(); - handle = subscriber.subscribe(TestNonstringConfig.class, configId); + ConfigSubscriber subscriber = new ConfigSubscriber(); + ConfigHandle<TestNonstringConfig> handle = subscriber.subscribe(TestNonstringConfig.class, configId); subscriber.nextConfig(); handle.getConfig(); } diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java index d17a2ff61f4..21cdfbe7d30 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java @@ -33,8 +33,8 @@ public class ConfigSetSubscriptionTest { configSet, new TimingValues()); - assertTrue(c1.equals(c1)); - assertFalse(c1.equals(c2)); + assertEquals(c1, c1); + assertNotEquals(c1, c2); } @Test(expected = IllegalArgumentException.class) diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java index b45f30d244d..38d4a6a4571 100755 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java @@ -15,25 +15,24 @@ import static org.junit.Assert.*; public class ConfigSourceSetTest { @Test public void testEquals() { - assertTrue(new ConfigSourceSet().equals(new ConfigSourceSet())); - assertFalse(new ConfigSourceSet().equals(new ConfigSourceSet(new String[]{"a"}))); + assertEquals(new ConfigSourceSet(), new ConfigSourceSet()); + assertNotEquals(new ConfigSourceSet(), new ConfigSourceSet(new String[]{"a"})); - assertTrue(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"a"}))); - assertTrue(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{" A "}))); - assertTrue(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"A", "a"}))); - assertTrue(new ConfigSourceSet(new String[]{"A"}).equals(new ConfigSourceSet(new String[]{"a", " a "}))); + assertEquals(new ConfigSourceSet(new String[]{"a"}), new ConfigSourceSet(new String[]{"a"})); + assertEquals(new ConfigSourceSet(new String[]{"a"}), new ConfigSourceSet(new String[]{" A "})); + assertEquals(new ConfigSourceSet(new String[]{"a"}), new ConfigSourceSet(new String[]{"A", "a"})); + assertEquals(new ConfigSourceSet(new String[]{"A"}), new ConfigSourceSet(new String[]{"a", " a "})); - assertFalse(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"b"}))); - assertFalse(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"a", "b"}))); + assertNotEquals(new ConfigSourceSet(new String[]{"a"}), new ConfigSourceSet(new String[]{"b"})); + assertNotEquals(new ConfigSourceSet(new String[]{"a"}), new ConfigSourceSet(new String[]{"a", "b"})); - assertTrue(new ConfigSourceSet(new String[]{"a", "b"}).equals(new ConfigSourceSet(new String[]{"a", "b"}))); - assertTrue(new ConfigSourceSet(new String[]{"b", "a"}).equals(new ConfigSourceSet(new String[]{"a", "b"}))); - assertTrue(new ConfigSourceSet(new String[]{"A", " b"}).equals(new ConfigSourceSet(new String[]{"a ", "B"}))); - assertTrue(new ConfigSourceSet(new String[]{"b", "a", "c"}) - .equals(new ConfigSourceSet(new String[]{"a", "b", "c"}))); + assertEquals(new ConfigSourceSet(new String[]{"a", "b"}), new ConfigSourceSet(new String[]{"a", "b"})); + assertEquals(new ConfigSourceSet(new String[]{"b", "a"}), new ConfigSourceSet(new String[]{"a", "b"})); + assertEquals(new ConfigSourceSet(new String[]{"A", " b"}), new ConfigSourceSet(new String[]{"a ", "B"})); + assertEquals(new ConfigSourceSet(new String[]{"b", "a", "c"}), new ConfigSourceSet(new String[]{"a", "b", "c"})); - assertFalse(new ConfigSourceSet(new String[]{"a", "b"}).equals(new ConfigSourceSet(new String[]{"b", "c"}))); - assertFalse(new ConfigSourceSet().equals("foo")); + assertNotEquals(new ConfigSourceSet(new String[]{"a", "b"}), new ConfigSourceSet(new String[]{"b", "c"})); + assertNotEquals("foo", new ConfigSourceSet()); } @Test diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java index 3aa422eb116..933a9fd130a 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java @@ -3,7 +3,6 @@ package com.yahoo.config.subscription; import com.yahoo.config.ConfigInstance; import com.yahoo.config.ConfigurationRuntimeException; -import com.yahoo.config.subscription.impl.GenericConfigHandle; import com.yahoo.foo.SimpletypesConfig; import com.yahoo.foo.AppConfig; import com.yahoo.config.subscription.impl.ConfigSubscription; @@ -59,8 +58,8 @@ public class ConfigSubscriptionTest { configSet, new TimingValues()); - assertTrue(c1.equals(c1)); - assertFalse(c1.equals(c2)); + assertEquals(c1, c1); + assertNotEquals(c1, c2); } @Test diff --git a/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java b/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java index 5c535f6a5fa..b32cdcd8c16 100644 --- a/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java @@ -178,7 +178,7 @@ public class FunctionTest { } //System.out.println("Config lacking " + param + "-> " + config + "\n"); try { - ConfigGetter<FunctionTestConfig> getter = new ConfigGetter<FunctionTestConfig>(FunctionTestConfig.class); + ConfigGetter<FunctionTestConfig> getter = new ConfigGetter<>(FunctionTestConfig.class); getter.getConfig("raw:\n" + config); if (isArray) { // Arrays are empty by default @@ -210,7 +210,7 @@ public class FunctionTest { assertEquals(1, config.boolarr().size()); assertEquals(1, config.boolarr().size()); // new api with accessor for a List of the original Java type assertEquals(false, config.boolarr().get(0)); // new List api - assertEquals(false, config.boolarr(0)); // short-hand + assertFalse(config.boolarr(0)); // short-hand assertEquals(0, config.intarr().size()); assertEquals(2, config.longarr().size()); assertEquals(Long.MAX_VALUE, config.longarr(0)); @@ -239,9 +239,9 @@ public class FunctionTest { assertEquals("inner1", config.rootStruct().inner1().name()); assertEquals(12, config.rootStruct().inner1().index()); assertEquals(2, config.rootStruct().innerArr().size()); - assertEquals(true, config.rootStruct().innerArr(0).boolVal()); + assertTrue(config.rootStruct().innerArr(0).boolVal()); assertEquals("deep", config.rootStruct().innerArr(0).stringVal()); - assertEquals(false, config.rootStruct().innerArr(1).boolVal()); + assertFalse(config.rootStruct().innerArr(1).boolVal()); assertEquals("blue a=\"escaped\"", config.rootStruct().innerArr(1).stringVal()); assertEquals(2, config.myarray().size()); // new List api diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java index c1e5cfb8f0f..c77985c91d8 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java @@ -82,10 +82,10 @@ public class ConfigBuilderMergeTest { public void require_that_struct_fields_are_overwritten() { String name1 = "foo"; String gender1 = "MALE"; - String emails1[] = { "foo@bar", "bar@foo" }; + String[] emails1 = {"foo@bar", "bar@foo"}; String name2 = "bar"; String gender2 = "FEMALE"; - String emails2[] = { "foo@bar", "bar@foo" }; + String[] emails2 = {"foo@bar", "bar@foo"}; StructtypesConfig.Builder b1 = createSimpleStructBuilder(name1, gender1, emails1); StructtypesConfig.Builder b2 = createSimpleStructBuilder(name2, gender2, emails2); ConfigInstanceUtil.setValues(b1, b2); diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java index bb4505b1250..bb65fdaa153 100755 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java @@ -24,11 +24,11 @@ public class ConfigCacheKeyTest { ConfigCacheKey k5 = new ConfigCacheKey("foo", "id", "ns_1", null); // test with null defMd5 final ConfigKey<?> configKey = new ConfigKey<>("foo", "id", "ns"); ConfigCacheKey k1_2 = new ConfigCacheKey(configKey, defMd5); - assertTrue(k1.equals(k1)); - assertTrue(k1.equals(k1_2)); - assertTrue(k1.equals(k2)); - assertFalse(k3.equals(k2)); - assertFalse(k4.equals(k1)); + assertEquals(k1, k1); + assertEquals(k1, k1_2); + assertEquals(k1, k2); + assertNotEquals(k3, k2); + assertNotEquals(k4, k1); assertThat(k1.hashCode(), is(k2.hashCode())); assertThat(k1.getDefMd5(), is(defMd5)); assertThat(k1.toString(), is(configKey.toString() + "," + defMd5)); diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java index f1220143b28..dba73223097 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java @@ -26,7 +26,7 @@ public class ConfigDefinitionBuilderTest { @Test // TODO Test ranges - public void testCreateConfigDefinition() throws IOException, InterruptedException { + public void testCreateConfigDefinition() throws IOException { File defFile = new File(DEF_NAME); DefParser defParser = new DefParser(defFile.getName(), new FileReader(defFile)); CNode root = defParser.getTree(); @@ -122,8 +122,8 @@ public class ConfigDefinitionBuilderTest { assertEquals(def.getLeafMapDefs().get("intMap").getTypeSpec().getType(), "int"); assertEquals(def.getLeafMapDefs().get("stringMap").getTypeSpec().getType(), "string"); assertEquals(def.getStructMapDefs().size(), 1); - assertEquals(def.getStructMapDefs().get("myStructMap").getIntDefs().get("myInt").getDefVal(), null); - assertEquals(def.getStructMapDefs().get("myStructMap").getStringDefs().get("myString").getDefVal(), null); + assertNull(def.getStructMapDefs().get("myStructMap").getIntDefs().get("myInt").getDefVal()); + assertNull(def.getStructMapDefs().get("myStructMap").getStringDefs().get("myString").getDefVal()); assertEquals(def.getStructMapDefs().get("myStructMap").getIntDefs().get("myIntDef").getDefVal(), (Integer)56); assertEquals(def.getStructMapDefs().get("myStructMap").getStringDefs().get("myStringDef").getDefVal(), "g"); diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java index c4024a73c97..4f5291c6a36 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java @@ -20,10 +20,10 @@ public class ConfigDefinitionKeyTest { assertEquals("foo", def1.getName()); assertEquals("fuz", def1.getNamespace()); - assertTrue(def1.equals(def1)); - assertFalse(def1.equals(def2)); - assertFalse(def1.equals(new Object())); - assertTrue(def2.equals(def2)); + assertEquals(def1, def1); + assertNotEquals(def1, def2); + assertNotEquals(def1, new Object()); + assertEquals(def2, def2); } @Test diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java deleted file mode 100644 index 1dc70f3c1a5..00000000000 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java +++ /dev/null @@ -1,55 +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; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Class to hold config definitions and resolving requests for the correct definition - * - * @author hmusum - */ -public class ConfigDefinitionSetTest { - - @Test - public void testBasic() { - ConfigDefinitionSet configDefinitionSet = new ConfigDefinitionSet(); - ConfigDefinition def1 = new ConfigDefinition("foo", "1"); - ConfigDefinition def2 = new ConfigDefinition("foo", "1", "namespace1"); - ConfigDefinition def3 = new ConfigDefinition("foo", "1", "namespace2"); - final ConfigDefinitionKey key1 = new ConfigDefinitionKey(def1.getName(), def1.getNamespace()); - configDefinitionSet.add(key1, def1); - ConfigDefinitionKey key2 = new ConfigDefinitionKey(def2.getName(), def2.getNamespace()); - configDefinitionSet.add(key2, def2); - ConfigDefinitionKey key3 = new ConfigDefinitionKey(def3.getName(), def3.getNamespace()); - configDefinitionSet.add(key3, def3); - assertEquals(3, configDefinitionSet.size()); - assertEquals(def1, configDefinitionSet.get(key1)); - assertEquals(def2, configDefinitionSet.get(key2)); - assertEquals(def3, configDefinitionSet.get(key3)); - - String str = configDefinitionSet.toString(); - assertTrue(str.contains("namespace1.foo")); - assertTrue(str.contains("namespace2.foo")); - assertTrue(str.contains("config.foo")); - } - - @Test - public void testFallbackToDefaultNamespace() { - ConfigDefinitionSet configDefinitionSet = new ConfigDefinitionSet(); - ConfigDefinition def1 = new ConfigDefinition("foo", "1"); - ConfigDefinition def2 = new ConfigDefinition("bar", "1", "namespace"); - - configDefinitionSet.add(new ConfigDefinitionKey(def1.getName(), def1.getNamespace()), def1); - configDefinitionSet.add(new ConfigDefinitionKey(def2.getName(), def2.getNamespace()), def2); - - // fallback to default namespace - assertEquals(def1, configDefinitionSet.get(new ConfigDefinitionKey("foo", "namespace"))); - // Should not fallback to some other config with same name, but different namespace (not default namespace) - assertNull(configDefinitionSet.get(new ConfigDefinitionKey("bar", "someothernamespace"))); - } - -} diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java index 01bdf4e0ad8..88e7e766974 100755 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java @@ -47,23 +47,8 @@ public class ConfigDefinitionTest { } @Test - public void testDefNumberCompare() { - ConfigDefinition df1 = new ConfigDefinition("d", "25"); - ConfigDefinition df2 = new ConfigDefinition("d", "5"); - ConfigDefinition df3 = new ConfigDefinition("d", "1-1"); - ConfigDefinition df4 = new ConfigDefinition("d", "0-2-3"); - ConfigDefinition df5 = new ConfigDefinition("d", "1"); - ConfigDefinition df6 = new ConfigDefinition("d", "1-0"); - assertTrue(df1.compareTo(df2) > 0); - assertTrue(df2.compareTo(df4) > 0); - assertEquals(1, df3.compareTo(df4)); - assertEquals(-1, df4.compareTo(df5)); - assertEquals(0, df5.compareTo(df6)); - } - - @Test public void testIntDefaultValues() { - ConfigDefinition def = new ConfigDefinition("foo", "1"); + ConfigDefinition def = new ConfigDefinition("foo", "1", "namespace1"); def.addIntDef("foo"); def.addIntDef("bar", 0); @@ -83,7 +68,7 @@ public class ConfigDefinitionTest { @Test public void testLongDefaultValues() { - ConfigDefinition def = new ConfigDefinition("foo", "1"); + ConfigDefinition def = new ConfigDefinition("foo", "1", "namespace1"); def.addLongDef("foo"); def.addLongDef("bar", 1234567890123L); @@ -102,7 +87,7 @@ public class ConfigDefinitionTest { @Test @SuppressWarnings("serial") public void testDefaultsPayloadMap() { - ConfigDefinition def = new ConfigDefinition("foo", "1"); + ConfigDefinition def = new ConfigDefinition("foo", "1", "namespace1"); def.addStringDef("mystring"); def.addStringDef("mystringdef", "foo"); def.addBoolDef("mybool"); @@ -113,8 +98,14 @@ public class ConfigDefinitionTest { def.addLongDef("mylongdef", 11L); def.addDoubleDef("mydouble"); def.addDoubleDef("mydoubledef", 2d); - EnumDef ed = new EnumDef(new ArrayList<String>(){{add("a1"); add("a2");}}, null); - EnumDef eddef = new EnumDef(new ArrayList<String>(){{add("a11"); add("a22");}}, "a22"); + EnumDef ed = new EnumDef(new ArrayList<>() {{ + add("a1"); + add("a2"); + }}, null); + EnumDef eddef = new EnumDef(new ArrayList<>() {{ + add("a11"); + add("a22"); + }}, "a22"); def.addEnumDef("myenum", ed); def.addEnumDef("myenumdef", eddef); def.addReferenceDef("myref"); diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java index 27d907d279d..3cc030d944b 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java @@ -17,6 +17,7 @@ import org.junit.Ignore; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @@ -137,7 +138,7 @@ public class ConfigFileFormatterTest { InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); new ConfigFileFormat(def).encode(baos, slime); - assertThat(baos.toString("UTF-8"), is("enumval null\nintval null\nlongval null\nboolval false\ndoubleval null\n")); + assertThat(baos.toString(StandardCharsets.UTF_8), is("enumval null\nintval null\nlongval null\nboolval false\ndoubleval null\n")); } // TODO: Reenable this when we can reenable typechecking. @@ -160,7 +161,7 @@ public class ConfigFileFormatterTest { ByteArrayOutputStream baos = new ByteArrayOutputStream(); InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree(); new ConfigFileFormat(def).encode(baos, slime); - assertThat(baos.toString("UTF-8"), is("stringval \"" + value + "\"\n")); + assertThat(baos.toString(StandardCharsets.UTF_8), is("stringval \"" + value + "\"\n")); } @Test @@ -326,7 +327,7 @@ public class ConfigFileFormatterTest { assertThat(Utf8.toString(baos.toByteArray()), is("stringval \"" + input + "\"\n")); } - public static String bytesToHexString(byte[] bytes){ + private static String bytesToHexString(byte[] bytes){ StringBuilder sb = new StringBuilder(); for(byte b : bytes){ sb.append(String.format("%02x", b&0xff)); diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java deleted file mode 100644 index 040cb06d05f..00000000000 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java +++ /dev/null @@ -1,31 +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; - -import com.yahoo.config.subscription.ConfigSourceSet; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -/** - * @author hmusum - * @since 5.1.9 - */ -public class ConfigHelperTest { - - @Test - public void basic() { - ConfigSourceSet configSourceSet = new ConfigSourceSet("host.com"); - ConfigHelper helper = new ConfigHelper(configSourceSet); - assertThat(helper.getConfigSourceSet(), is(configSourceSet)); - assertThat(helper.getConnectionPool().getAllSourceAddresses(), is("host.com")); - assertThat(helper.getTimingValues().getSubscribeTimeout(), is(new TimingValues().getSubscribeTimeout())); - - // Specify timing values - TimingValues tv = new TimingValues(); - tv.setSubscribeTimeout(11L); - helper = new ConfigHelper(configSourceSet, tv); - assertThat(helper.getTimingValues().getSubscribeTimeout(), is(tv.getSubscribeTimeout())); - } - -} diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java index 0642d5733c9..427014316cf 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java @@ -27,16 +27,16 @@ public class ConfigKeyTest { assertEquals(key1, key2); ConfigKey<?> key3 = new ConfigKey<>("foo", "a/b/c/d", namespace); - assertTrue(!key1.equals(key3)); assertFalse(key1.equals(key3)); + assertNotEquals(key1, key3); assertEquals("a/b/c", new ConfigKey<>("foo", "a/b/c", namespace).getConfigId()); assertEquals("a", new ConfigKey<>("foo", "a", namespace).getConfigId()); assertEquals("", new ConfigKey<>("foo", "", namespace).getConfigId()); - assertTrue(key1.equals(key1)); - assertFalse(key1.equals(key3)); - assertFalse(key1.equals(new Object())); + assertEquals(key1, key1); + assertNotEquals(key1, key3); + assertNotEquals(key1, new Object()); ConfigKey<?> key4 = new ConfigKey<>("myConfig", null, namespace); assertEquals("", key4.getConfigId()); @@ -70,11 +70,11 @@ public class ConfigKeyTest { ConfigKey<?> noNamespace = new ConfigKey<>("name", "id", null); ConfigKey<?> namespaceFoo = new ConfigKey<>("name", "id", "foo"); ConfigKey<?> namespaceBar = new ConfigKey<>("name", "id", "bar"); - assertTrue(noNamespace.equals(noNamespace)); - assertTrue(namespaceFoo.equals(namespaceFoo)); - assertFalse(noNamespace.equals(namespaceFoo)); - assertFalse(namespaceFoo.equals(noNamespace)); - assertFalse(namespaceFoo.equals(namespaceBar)); + assertEquals(noNamespace, noNamespace); + assertEquals(namespaceFoo, namespaceFoo); + assertNotEquals(noNamespace, namespaceFoo); + assertNotEquals(namespaceFoo, noNamespace); + assertNotEquals(namespaceFoo, namespaceBar); assertEquals(noNamespace.getNamespace(), CNode.DEFAULT_NAMESPACE); assertEquals(namespaceBar.getNamespace(), "bar"); } diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java index 63a55d20edf..f1b0adc03e7 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java @@ -14,7 +14,6 @@ import com.yahoo.slime.Slime; import com.yahoo.text.StringUtilities; import org.junit.Test; -import java.io.IOException; import java.io.StringReader; import static org.hamcrest.CoreMatchers.is; @@ -31,13 +30,13 @@ import static org.junit.Assert.assertTrue; public class ConfigPayloadTest { @Test - public void test_simple_builder() throws Exception { + public void test_simple_builder() { SimpletypesConfig config = createSimpletypesConfig("stringval", "abcde"); assertThat(config.stringval(), is("abcde")); } @Test - public void require_that_arrays_are_built() throws Exception { + public void require_that_arrays_are_built() { AppConfig config = createAppConfig("foo", "4", new String[] { "bar", "baz", "bim" }); assertThat(config.message(), is("foo")); assertThat(config.times(), is(4)); @@ -47,7 +46,7 @@ public class ConfigPayloadTest { } @Test - public void test_int_leaf_legal() throws Exception { + public void test_int_leaf_legal() { SimpletypesConfig config = createSimpletypesConfig("intval", "0"); assertThat(config.intval(), is(0)); config = createSimpletypesConfig("intval", String.valueOf(Integer.MIN_VALUE)); @@ -61,27 +60,27 @@ public class ConfigPayloadTest { } @Test (expected = RuntimeException.class) - public void test_int_leaf_too_large() throws Exception { - createSimpletypesConfig("intval", String.valueOf(Integer.MAX_VALUE) + "00"); + public void test_int_leaf_too_large() { + createSimpletypesConfig("intval", Integer.MAX_VALUE + "00"); } @Test (expected = RuntimeException.class) - public void test_int_leaf_too_large_neg() throws Exception { - createSimpletypesConfig("intval", String.valueOf(Integer.MIN_VALUE) + "00"); + public void test_int_leaf_too_large_neg() { + createSimpletypesConfig("intval", Integer.MIN_VALUE + "00"); } @Test(expected=RuntimeException.class) - public void test_int_leaf_illegal_string() throws Exception { + public void test_int_leaf_illegal_string() { createSimpletypesConfig("intval", "illegal"); } @Test(expected=RuntimeException.class) - public void test_int_leaf_illegal_string_suffix() throws Exception { + public void test_int_leaf_illegal_string_suffix() { createSimpletypesConfig("intval", "123illegal"); } @Test(expected=RuntimeException.class) - public void test_int_leaf_illegal_string_prefix() throws Exception { + public void test_int_leaf_illegal_string_prefix() { createSimpletypesConfig("intval", "illegal123"); } @@ -95,7 +94,7 @@ public class ConfigPayloadTest { @Test - public void test_long_leaf() throws Exception { + public void test_long_leaf() { SimpletypesConfig config = createSimpletypesConfig("longval", "0"); assertThat(config.longval(), is(0L)); config = createSimpletypesConfig("longval", String.valueOf(Long.MIN_VALUE)); @@ -109,22 +108,22 @@ public class ConfigPayloadTest { } @Test(expected = RuntimeException.class) - public void test_long_leaf_illegal_string() throws Exception { + public void test_long_leaf_illegal_string() { createSimpletypesConfig("longval", "illegal"); } @Test (expected = RuntimeException.class) - public void test_long_leaf_too_large() throws Exception { - createSimpletypesConfig("longval", String.valueOf(Long.MAX_VALUE) + "00"); + public void test_long_leaf_too_large() { + createSimpletypesConfig("longval", Long.MAX_VALUE + "00"); } @Test (expected = RuntimeException.class) - public void test_long_leaf_too_large_neg() throws Exception { - createSimpletypesConfig("longval", String.valueOf(Long.MIN_VALUE) + "00"); + public void test_long_leaf_too_large_neg() { + createSimpletypesConfig("longval", Long.MIN_VALUE + "00"); } @Test - public void test_double_leaf() throws Exception { + public void test_double_leaf() { SimpletypesConfig config = createSimpletypesConfig("doubleval", "0"); assertEquals(0.0, config.doubleval(), 0.01); assertEquals(133.3, createSimpletypesConfig("doubleval", "133.3").doubleval(), 0.001); @@ -135,35 +134,35 @@ public class ConfigPayloadTest { } @Test - public void test_serializer() throws IOException { + public void test_serializer() { ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder())); assertThat(payload.toString(true), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}")); } @Test(expected=RuntimeException.class) - public void test_double_leaf_illegal_string() throws Exception { + public void test_double_leaf_illegal_string() { createSimpletypesConfig("doubleval", "illegal"); } @Test - public void test_double_leaf_negative_infinity() throws Exception { + public void test_double_leaf_negative_infinity() { assertThat(createSimpletypesConfig("doubleval", "-Infinity").doubleval(), is(Double.NEGATIVE_INFINITY)); assertThat(createSimpletypesConfig("doubleval", "Infinity").doubleval(), is(Double.POSITIVE_INFINITY)); } @Test - public void test_enum_leaf() throws Exception { + public void test_enum_leaf() { assertThat(createSimpletypesConfig("enumval", "VAL1").enumval(), is(SimpletypesConfig.Enumval.Enum.VAL1)); assertThat(createSimpletypesConfig("enumval", "VAL2").enumval(), is(SimpletypesConfig.Enumval.Enum.VAL2)); } @Test(expected=RuntimeException.class) - public void test_enum_leaf_illegal_string() throws Exception { + public void test_enum_leaf_illegal_string() { createSimpletypesConfig("enumval", "ILLEGAL"); } @Test - public void test_bool_leaf() throws Exception { + public void test_bool_leaf() { SimpletypesConfig config = createSimpletypesConfig("boolval", "true"); assertThat(config.boolval(), is(true)); config = createSimpletypesConfig("boolval", "false"); @@ -175,18 +174,18 @@ public class ConfigPayloadTest { } @Test// FIXME: (expected = RuntimeException.class) - public void test_bool_leaf_illegal() throws Exception { + public void test_bool_leaf_illegal() { createSimpletypesConfig("boolval", "illegal"); } @Test - public void test_string_illegal_value() throws Exception { + public void test_string_illegal_value() { // TODO: What do we consider illegal string values? createSimpletypesConfig("stringval", "insert_illegal_value_please"); } @Test - public void test_int_array() throws Exception { + public void test_int_array() { // Normal behavior ArraytypesConfig config = createArraytypesConfig("intarr", new String[] { "2", "3", "1", "-2", "5"}); assertThat(config.intarr().size(), is(5)); @@ -210,12 +209,12 @@ public class ConfigPayloadTest { } @Test(expected = RuntimeException.class) - public void test_int_array_illegal() throws Exception { + public void test_int_array_illegal() { createArraytypesConfig("intarr", new String[] { "2", "3", "illegal", "-2", "5"}); } @Test - public void test_long_array() throws Exception { + public void test_long_array() { // Normal behavior ArraytypesConfig config = createArraytypesConfig("longarr", new String[] { "2", "3", "1", "-2", "5"}); assertThat(config.longarr().size(), is(5)); @@ -239,7 +238,7 @@ public class ConfigPayloadTest { } @Test - public void test_double_array() throws Exception { + public void test_double_array() { // Normal behavior ArraytypesConfig config = createArraytypesConfig("doublearr", new String[] { "2.1", "3.3", "1.5", "-2.1", "Infinity"}); assertThat(config.doublearr().size(), is(5)); @@ -251,7 +250,7 @@ public class ConfigPayloadTest { } @Test - public void test_enum_array() throws Exception { + public void test_enum_array() { // Normal behavior ArraytypesConfig config = createArraytypesConfig("enumarr", new String[] { "VAL1", "VAL2", "VAL1" }); assertThat(config.enumarr().size(), is(3)); @@ -261,7 +260,7 @@ public class ConfigPayloadTest { } @Test - public void test_simple_struct() throws Exception { + public void test_simple_struct() { Slime slime = new Slime(); addStructFields(slime.setObject().setObject("simple"), "foobar", "MALE", new String[] { "foo@bar", "bar@foo" }); StructtypesConfig config = new ConfigPayload(slime).toInstance(StructtypesConfig.class, ""); @@ -273,7 +272,7 @@ public class ConfigPayloadTest { } @Test - public void test_simple_struct_arrays() throws Exception { + public void test_simple_struct_arrays() { StructtypesConfig config = createStructtypesConfigArray(new String[] { "foo", "bar" }, new String[] { "MALE", "FEMALE" }); assertThat(config.simplearr(0).name(), is("foo")); @@ -284,7 +283,7 @@ public class ConfigPayloadTest { @Test - public void test_nested_struct() throws Exception { + public void test_nested_struct() { StructtypesConfig config = createStructtypesConfigNested("foo", "FEMALE"); assertThat(config.nested().inner().name(), is("foo")); assertThat(config.nested().inner().gender(), is(StructtypesConfig.Nested.Inner.Gender.Enum.FEMALE)); @@ -293,7 +292,7 @@ public class ConfigPayloadTest { @Test - public void test_nested_struct_array() throws Exception { + public void test_nested_struct_array() { String [] names = { "foo" ,"bar" }; String [] genders = { "FEMALE", "MALE" }; String [][] emails = { @@ -314,7 +313,7 @@ public class ConfigPayloadTest { @Test - public void test_complex_struct_array() throws Exception { + public void test_complex_struct_array() { String [][] names = { { "foo", "bar" }, { "baz", "bim" } @@ -454,19 +453,19 @@ public class ConfigPayloadTest { } @Test - public void test_escaped_string() throws Exception { + public void test_escaped_string() { SimpletypesConfig config = createSimpletypesConfig("stringval", "b=\"escaped\""); assertThat(config.stringval(), is("b=\"escaped\"")); } @Test - public void test_unicode() throws Exception { + public void test_unicode() { SimpletypesConfig config = createSimpletypesConfig("stringval", "Hei \u00E6\u00F8\u00E5 \uBC14\uB451 \u00C6\u00D8\u00C5 hallo"); assertThat(config.stringval(), is("Hei \u00E6\u00F8\u00E5 \uBC14\uB451 \u00C6\u00D8\u00C5 hallo")); } @Test - public void test_empty_payload() throws Exception { + public void test_empty_payload() { Slime slime = new Slime(); slime.setObject(); IntConfig config = new ConfigPayload(slime).toInstance(IntConfig.class, ""); diff --git a/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java b/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java index faeedff2762..8a403b45003 100644 --- a/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java @@ -13,7 +13,7 @@ import static org.junit.Assert.*; /** * Tests for the JRTConnectionPool class. * - * @author <a href="mailto:gunnarga@yahoo-inc.com">Gunnar Gauslaa Bergem</a> + * @author Gunnar Gauslaa Bergem * @author hmusum */ public class JRTConnectionPoolTest { diff --git a/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java b/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java index b19da2c1689..a564fea8b2e 100644 --- a/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @@ -75,10 +76,10 @@ public class RawConfigTest { assertThat(config.getVespaVersion(), is(not(config3.getVespaVersion()))); // null config - assertFalse(config.equals(null)); + assertNotEquals(null, config); // different type of object - assertFalse(config.equals(key)); + assertNotEquals(config, key); // errors RawConfig errorConfig1 = new RawConfig(key, defMd5, payload, configMd5, generation, false, 1, defContent, Optional.empty()); diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java b/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java index d4c63ae35cd..e5fc5190ad1 100644 --- a/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java @@ -8,11 +8,10 @@ import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.LZ4PayloadCompressor; import org.junit.Test; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; /** * @author Ulf Lilleengen @@ -58,19 +57,15 @@ public class PayloadTest { Payload h = null; Payload i = null; Payload j = null; - try { - g = Payload.from(new Utf8Array(foo1.getBytes("UTF-8")), CompressionInfo.uncompressed()); - h = Payload.from(new Utf8Array(foo1.getBytes("UTF-8")), CompressionInfo.uncompressed()); + g = Payload.from(new Utf8Array(foo1.getBytes(StandardCharsets.UTF_8)), CompressionInfo.uncompressed()); + h = Payload.from(new Utf8Array(foo1.getBytes(StandardCharsets.UTF_8)), CompressionInfo.uncompressed()); - LZ4PayloadCompressor compressor = new LZ4PayloadCompressor(); - CompressionInfo info = CompressionInfo.create(CompressionType.LZ4, foo2.length()); - Utf8Array compressed = new Utf8Array(compressor.compress(foo2.getBytes())); + LZ4PayloadCompressor compressor = new LZ4PayloadCompressor(); + CompressionInfo info = CompressionInfo.create(CompressionType.LZ4, foo2.length()); + Utf8Array compressed = new Utf8Array(compressor.compress(foo2.getBytes())); - i = Payload.from(compressed, info); - j = Payload.from(compressed, info); - } catch (UnsupportedEncodingException e1) { - fail(); - } + i = Payload.from(compressed, info); + j = Payload.from(compressed, info); new EqualsTester() .addEqualityGroup(a, b, g, h) diff --git a/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java b/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java index 0b615e59179..190c7479ee7 100644 --- a/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java @@ -67,18 +67,18 @@ public class ConfigUtilsTest { lines.add("foo=\"1#hello\""); lines.add(""); //empty line should not affect md5sum - assertThat(ConfigUtils.getMd5(lines), is(expectedMd5)); + assertThat(getMd5(lines), is(expectedMd5)); lines.clear(); // Check that comment character in string leads to a different md5 than the original lines.add("foo=\"1#hello and some more\""); - String md5 = ConfigUtils.getMd5(lines); + String md5 = getMd5(lines); assertThat(md5, is(not(expectedMd5))); // Check that added characters aft comment character in string leads to a different md5 than above lines.add("foo=\"1#hello and some more and even more\""); - assertThat(ConfigUtils.getMd5(lines), is(not(md5))); + assertThat(getMd5(lines), is(not(md5))); } @Test @@ -114,22 +114,6 @@ public class ConfigUtilsTest { } @Test - public void testGetVersion() { - StringReader reader = new StringReader("version=1\nint a default=0"); - assertThat(ConfigUtils.getDefVersion(reader), is("1")); - - // no version - reader = new StringReader("int a default=0"); - assertThat(ConfigUtils.getDefVersion(reader), is("")); - - // namespace and version - reader = new StringReader("version=1\nnamespace=foo\nint a default=0"); - assertThat(ConfigUtils.getDefVersion(reader), is("1")); - reader = new StringReader("namespace=foo\nversion=1\nint a default=0"); - assertThat(ConfigUtils.getDefVersion(reader), is("1")); - } - - @Test public void testGetNamespace() { StringReader reader = new StringReader("version=1\nnamespace=a\nint a default=0"); assertThat(ConfigUtils.getDefNamespace(reader), is("a")); @@ -154,26 +138,6 @@ public class ConfigUtilsTest { } @Test - public void testGetNameCommaVersion() { - String nameCommaversion = "foo,1"; - Tuple2<String, String> tuple = ConfigUtils.getNameAndVersionFromString(nameCommaversion); - assertThat(tuple.first, is("foo")); - assertThat(tuple.second, is("1")); - - // no version - nameCommaversion = "foo"; - tuple = ConfigUtils.getNameAndVersionFromString(nameCommaversion); - assertThat(tuple.first, is("foo")); - assertThat(tuple.second, is("")); - - // no name - nameCommaversion = ",1"; - tuple = ConfigUtils.getNameAndVersionFromString(nameCommaversion); - assertThat(tuple.first, is("")); - assertThat(tuple.second, is("1")); - } - - @Test public void testNamespaceDotNames() { String namespaceDotName = "foo.bar"; Tuple2<String, String> tuple = ConfigUtils.getNameAndNamespaceFromString(namespaceDotName); @@ -205,29 +169,18 @@ public class ConfigUtilsTest { } @Test - public void testGetConfigDefinitionKey() { - String input = "foo.bar"; - ConfigDefinitionKey def = ConfigUtils.getConfigDefinitionKeyFromString(input); - assertThat(def.getName(), is("bar")); - assertThat(def.getNamespace(), is("foo")); - - input = "foo.bar.1"; - def = ConfigUtils.getConfigDefinitionKeyFromString(input); - assertThat(def.getName(), is("bar")); - assertThat(def.getNamespace(), is("foo")); + public void testCreateConfigDefinitionKeyFromZKString() { + ConfigDefinitionKey def1 = ConfigUtils.createConfigDefinitionKeyFromZKString("bar.foo,1"); + assertThat(def1.getName(), is("foo")); + assertThat(def1.getNamespace(), is("bar")); - input = "foo.bar.qux.2"; - def = ConfigUtils.getConfigDefinitionKeyFromString(input); - assertThat(def.getName(), is("qux")); - assertThat(def.getNamespace(), is("foo.bar")); - } + ConfigDefinitionKey def2 = ConfigUtils.createConfigDefinitionKeyFromZKString("bar.foo,"); + assertThat(def2.getName(), is("foo")); + assertThat(def2.getNamespace(), is("bar")); - @Test - public void testCreateConfigDefinitionKeyFromZKString() { - String input = "bar.foo,1"; - ConfigDefinitionKey def = ConfigUtils.createConfigDefinitionKeyFromZKString(input); - assertThat(def.getName(), is("foo")); - assertThat(def.getNamespace(), is("bar")); + ConfigDefinitionKey def3 = ConfigUtils.createConfigDefinitionKeyFromZKString("bar.foo"); + assertThat(def3.getName(), is("foo")); + assertThat(def3.getNamespace(), is("bar")); } @Test @@ -270,4 +223,23 @@ public class ConfigUtilsTest { assertThat(def.getNamespace(), is("mynamespace")); } + /** + * Computes Md5 hash of a list of strings. The only change to input lines before + * computing md5 is to skip empty lines. + * + * @param lines A list of lines + * @return the Md5 hash of the list, with lowercase letters + */ + private static String getMd5(List<String> lines) { + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + // Remove empty lines + line = line.trim(); + if (line.length() > 0) { + sb.append(line).append("\n"); + } + } + return ConfigUtils.getMd5(sb.toString()); + } + } diff --git a/config/src/testrun/.gitignore b/config/src/testrun/.gitignore deleted file mode 100644 index faed45bc94a..00000000000 --- a/config/src/testrun/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -test-report.html -test-report.html.* -test.*.*.desc -test.*.*.file.* -test.*.*.files.html -test.*.*.log -tmp.* -xsync.log -/test.*.*.result -Makefile diff --git a/config/src/vespa/config/subscription/confighandle.hpp b/config/src/vespa/config/subscription/confighandle.hpp index f79944f153a..df4f01d0b40 100644 --- a/config/src/vespa/config/subscription/confighandle.hpp +++ b/config/src/vespa/config/subscription/confighandle.hpp @@ -13,7 +13,7 @@ template <typename ConfigType> std::unique_ptr<ConfigType> ConfigHandle<ConfigType>::getConfig() const { - return _subscription->getConfig().newInstance<ConfigType>(); + return _subscription->getConfig().template newInstance<ConfigType>(); } template <typename ConfigType> diff --git a/configgen/bin/make-config.pl b/configgen/bin/make-config.pl deleted file mode 100755 index 13017a679e3..00000000000 --- a/configgen/bin/make-config.pl +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env perl -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -# -# This script transforms a .def file into a .h and .cpp file for config -# -# TODO: Remove this script and use the java code directly. BTW, why -# does this script have the same limitations as the old make-config.pl -# in that the .def-file must reside in the root directory? The java -# code supports reading the .def-file from any directory. This script -# should have the same input parameters as the java code, and just -# map them directly to the java system properties - -# BEGIN perl environment bootstrap section -# Do not edit between here and END as this section should stay identical in all scripts - -use File::Basename; -use File::Path; - -sub findpath { - my $myfullname = ${0}; - my($myname, $mypath) = fileparse($myfullname); - - return $mypath if ( $mypath && -d $mypath ); - $mypath=`pwd`; - - my $pwdfullname = $mypath . "/" . $myname; - return $mypath if ( -f $pwdfullname ); - return 0; -} - -# Returns the argument path if it seems to point to VESPA_HOME, 0 otherwise -sub is_vespa_home { - my($VESPA_HOME) = shift; - my $COMMON_ENV="libexec/vespa/common-env.sh"; - if ( $VESPA_HOME && -d $VESPA_HOME ) { - my $common_env = $VESPA_HOME . "/" . $COMMON_ENV; - return $VESPA_HOME if -f $common_env; - } - return 0; -} - -# Returns the home of Vespa, or dies if it cannot -sub findhome { - # Try the VESPA_HOME env variable - return $ENV{'VESPA_HOME'} if is_vespa_home($ENV{'VESPA_HOME'}); - if ( $ENV{'VESPA_HOME'} ) { # was set, but not correctly - die "FATAL: bad VESPA_HOME value '" . $ENV{'VESPA_HOME'} . "'\n"; - } - - # Try the ROOT env variable - $ROOT = $ENV{'ROOT'}; - return $ROOT if is_vespa_home($ROOT); - - # Try the script location or current dir - my $mypath = findpath(); - if ($mypath) { - while ( $mypath =~ s|/[^/]*$|| ) { - return $mypath if is_vespa_home($mypath); - } - } - die "FATAL: Missing VESPA_HOME environment variable\n"; -} - -sub findhost { - my $tmp = $ENV{'VESPA_HOSTNAME'}; - my $bin = $ENV{'VESPA_HOME'} . "/bin"; - if (!defined $tmp) { - $tmp = `${bin}/vespa-detect-hostname || hostname -f || hostname || echo "localhost"`; - chomp $tmp; - } - my $validate = "${bin}/vespa-validate-hostname"; - if (-f "${validate}") { - system("${validate} $tmp"); - ( $? == 0 ) or die "Could not validate hostname\n"; - } - return $tmp; -} - -BEGIN { - my $tmp = findhome(); - $ENV{'VESPA_HOME'} = $tmp; - $tmp = findhost(); - $ENV{'VESPA_HOSTNAME'} = $tmp; -} -my $VESPA_HOME = $ENV{'VESPA_HOME'}; - -# END perl environment bootstrap section - -use lib $ENV{'VESPA_HOME'} . '/lib/perl5/site_perl'; -use lib $ENV{'VESPA_HOME'} . '/lib64/perl5/site_perl'; -use Yahoo::Vespa::Defaults; -readConfFile(); - -require 5.006_001; -use strict; -use warnings; - -use Cwd 'abs_path'; - -# Now this uses the new java codegen library. But the script still exist to -# map be able to call java the right way, setting the necessary properties - -my ($root, $def) = @ARGV; - -if (!defined $root || !defined $def) { - print "Usage make-config.pl <source root dir> <def file>\n"; - exit(1); -} - -#print "Root: $root\n" -# . "Def: $def\n"; - -my $subdir = &getRelativePath($root, &getPath($def)); - -my $cmd = "java" - . " -Dconfig.spec=$def" - . " -Dconfig.dest=$root" - . " -Dconfig.lang=cppng" - . " -Dconfig.requireNamespace=false" - . " -Dconfig.subdir=$subdir" - . " -Dconfig.dumpTree=false" - . " -Xms64m -Xmx64m" - . " -jar $VESPA_HOME/lib/jars/configgen.jar"; - -print "Generating config: $cmd\n"; -exec($cmd); - -exit(0); # Will never be called due to exec above, but just to indicate end - -sub getRelativePath { - my ($from, $to) = @_; - - $from = abs_path($from); - $to = abs_path($to); - - # Escape $from so it can contain regex special characters in path - $from =~ s/([\+\*\(\)\{\}\.\?\[\]\$\&])/\\$1/g; - - $to =~ /^$from\/(.*)$/ - or die "The def file must be contained within the root"; - return $1; -} - -sub getPath { - my $file = $_[0]; - if ($file =~ /^(.*)\/[^\/]*$/) { - return $1; - } else { - return "."; - } -} diff --git a/configgen/bin/make-configold.pl b/configgen/bin/make-configold.pl deleted file mode 100755 index d299d09d3be..00000000000 --- a/configgen/bin/make-configold.pl +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env perl -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -# -# This script transforms a .def file into a .h and .cpp file for config -# -# TODO: Remove this script and use the java code directly. BTW, why -# does this script have the same limitations as the old make-config.pl -# in that the .def-file must reside in the root directory? The java -# code supports reading the .def-file from any directory. This script -# should have the same input parameters as the java code, and just -# map them directly to the java system properties - -# BEGIN perl environment bootstrap section -# Do not edit between here and END as this section should stay identical in all scripts - -use File::Basename; -use File::Path; - -sub findpath { - my $myfullname = ${0}; - my($myname, $mypath) = fileparse($myfullname); - - return $mypath if ( $mypath && -d $mypath ); - $mypath=`pwd`; - - my $pwdfullname = $mypath . "/" . $myname; - return $mypath if ( -f $pwdfullname ); - return 0; -} - -# Returns the argument path if it seems to point to VESPA_HOME, 0 otherwise -sub is_vespa_home { - my($VESPA_HOME) = shift; - my $COMMON_ENV="libexec/vespa/common-env.sh"; - if ( $VESPA_HOME && -d $VESPA_HOME ) { - my $common_env = $VESPA_HOME . "/" . $COMMON_ENV; - return $VESPA_HOME if -f $common_env; - } - return 0; -} - -# Returns the home of Vespa, or dies if it cannot -sub findhome { - # Try the VESPA_HOME env variable - return $ENV{'VESPA_HOME'} if is_vespa_home($ENV{'VESPA_HOME'}); - if ( $ENV{'VESPA_HOME'} ) { # was set, but not correctly - die "FATAL: bad VESPA_HOME value '" . $ENV{'VESPA_HOME'} . "'\n"; - } - - # Try the ROOT env variable - $ROOT = $ENV{'ROOT'}; - return $ROOT if is_vespa_home($ROOT); - - # Try the script location or current dir - my $mypath = findpath(); - if ($mypath) { - while ( $mypath =~ s|/[^/]*$|| ) { - return $mypath if is_vespa_home($mypath); - } - } - die "FATAL: Missing VESPA_HOME environment variable\n"; -} - -sub findhost { - my $tmp = $ENV{'VESPA_HOSTNAME'}; - my $bin = $ENV{'VESPA_HOME'} . "/bin"; - if (!defined $tmp) { - $tmp = `${bin}/vespa-detect-hostname || hostname -f || hostname || echo "localhost"`; - chomp $tmp; - } - my $validate = "${bin}/vespa-validate-hostname"; - if (-f "${validate}") { - system("${validate} $tmp"); - ( $? == 0 ) or die "Could not validate hostname\n"; - } - return $tmp; -} - -BEGIN { - my $tmp = findhome(); - $ENV{'VESPA_HOME'} = $tmp; - $tmp = findhost(); - $ENV{'VESPA_HOSTNAME'} = $tmp; -} -my $VESPA_HOME = $ENV{'VESPA_HOME'}; - -# END perl environment bootstrap section - -use lib $ENV{'VESPA_HOME'} . '/lib/perl5/site_perl'; -use lib $ENV{'VESPA_HOME'} . '/lib64/perl5/site_perl'; -use Yahoo::Vespa::Defaults; -readConfFile(); - -require 5.006_001; -use strict; -use warnings; - -use Cwd 'abs_path'; - -# Now this uses the new java codegen library. But the script still exist to -# map be able to call java the right way, setting the necessary properties - -my ($root, $def) = @ARGV; - -if (!defined $root || !defined $def) { - print "Usage make-config.pl <source root dir> <def file>\n"; - exit(1); -} - -#print "Root: $root\n" -# . "Def: $def\n"; - -my $subdir = &getRelativePath($root, &getPath($def)); - -my $cmd = "java" - . " -Dconfig.spec=$def" - . " -Dconfig.dest=$root" - . " -Dconfig.lang=cpp" - . " -Dconfig.subdir=$subdir" - . " -Dconfig.dumpTree=false" - . " -Xms64m -Xmx64m" - . " -jar $VESPA_HOME/lib/jars/configgen.jar"; - -print "Generating config: $cmd\n"; -exec($cmd); - -exit(0); # Will never be called due to exec above, but just to indicate end - -sub getRelativePath { - my ($from, $to) = @_; - - $from = abs_path($from); - $to = abs_path($to); - - # Escape $from so it can contain regex special characters in path - $from =~ s/([\+\*\(\)\{\}\.\?\[\]\$\&])/\\$1/g; - - $to =~ /^$from\/(.*)$/ - or die "The def file must be contained within the root"; - return $1; -} - -sub getPath { - my $file = $_[0]; - if ($file =~ /^(.*)\/[^\/]*$/) { - return $1; - } else { - return "."; - } -} diff --git a/configgen/src/main/java/com/yahoo/config/codegen/CppClassBuilder.java b/configgen/src/main/java/com/yahoo/config/codegen/CppClassBuilder.java index 216a6b50981..bc8369677fc 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/CppClassBuilder.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/CppClassBuilder.java @@ -272,7 +272,7 @@ public class CppClassBuilder implements ClassBuilder { String typeName = getTypeName(child, false); declaredTypes.add(child.getName()); if (child instanceof LeafCNode.EnumLeaf) { - w.write(indent + "enum " + typeName + " { "); + w.write(indent + "enum class " + typeName + " { "); LeafCNode.EnumLeaf leaf = (LeafCNode.EnumLeaf) child; for (int i=0; i<leaf.getLegalValues().length; ++i) { if (i != 0) { @@ -313,7 +313,6 @@ public class CppClassBuilder implements ClassBuilder { void writeHeaderFunctionDeclarations(Writer w, String className, CNode node, String indent) throws IOException { w.write("" + indent + "const vespalib::string & defName() const override { return CONFIG_DEF_NAME; }\n" - + indent + "const vespalib::string & defVersion() const { return CONFIG_DEF_VERSION; }\n" + indent + "const vespalib::string & defMd5() const override { return CONFIG_DEF_MD5; }\n" + indent + "const vespalib::string & defNamespace() const override { return CONFIG_DEF_NAMESPACE; }\n" + indent + "void serialize(::config::ConfigDataBuffer & __buffer) const override;\n"); @@ -624,7 +623,7 @@ public class CppClassBuilder implements ClassBuilder { for (int i=0; i<leaf.getLegalValues().length; ++i) { w.write(" " + (i != 0 ? "} else " : "")); w.write("if (name == \"" + leaf.getLegalValues()[i] + "\") {\n" - + " return " + leaf.getLegalValues()[i] + ";\n"); + + " return " + typeName + "::" + leaf.getLegalValues()[i] + ";\n"); } w.write(" } else {\n" + " throw InvalidConfigException(\"Illegal enum value '\" + name + \"'\");\n" @@ -639,12 +638,12 @@ public class CppClassBuilder implements ClassBuilder { + " switch (t) {\n" ); for (int i=0; i<leaf.getLegalValues().length; ++i) { - w.write(" case " + leaf.getLegalValues()[i] + ": return \"" + leaf.getLegalValues()[i] + "\";\n"); + w.write(" case " + typeName + "::" + leaf.getLegalValues()[i] + ": return \"" + leaf.getLegalValues()[i] + "\";\n"); } w.write(" default:\n" + " {\n" + " vespalib::asciistream ost;\n" - + " ost << \"UNKNOWN(\" << t << \")\";\n" + + " ost << \"UNKNOWN(\" << static_cast<int>(t) << \")\";\n" + " return ost.str();\n" + " }\n" + " }\n" @@ -688,6 +687,9 @@ public class CppClassBuilder implements ClassBuilder { } else if (child instanceof LeafCNode) { // If we have a default value, use that.. LeafCNode leaf = (LeafCNode) child; if (leaf.getDefaultValue() != null) { + if (leaf.getType().equals("enum")) { + w.write(getTypeName(leaf, false) + "::"); + } w.write(getDefaultValue(leaf)); } else { // Defines empty constructor defaults for primitives without default set @@ -700,7 +702,7 @@ public class CppClassBuilder implements ClassBuilder { } else if (leaf.getType().equals("string")) { } else if (leaf.getType().equals("enum")) { LeafCNode.EnumLeaf enumNode = (LeafCNode.EnumLeaf) leaf; - w.write(enumNode.getLegalValues()[0]); + w.write(getTypeName(leaf, false) + "::" + enumNode.getLegalValues()[0]); } else if (leaf.getType().equals("reference")) { } else if (leaf.getType().equals("file")) { } @@ -1098,6 +1100,9 @@ public class CppClassBuilder implements ClassBuilder { if (child instanceof LeafCNode && ((LeafCNode) child).getDefaultValue() != null) { LeafCNode leaf = (LeafCNode) child; String defaultValue = getDefaultValue(leaf); + if (leaf.getType().equals("enum")) { + defaultValue = getTypeName(leaf, false) + "::" + defaultValue; + } w.write("()(" + childInspector + ", " + defaultValue + ");\n"); } else if (child instanceof InnerCNode) { w.write("()(" + childInspector + ");\n"); diff --git a/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java b/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java index 5c447191614..75149d7a50e 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java @@ -96,7 +96,7 @@ public class JavaClassBuilder implements ClassBuilder { " public final static String CONFIG_DEF_MD5 = \"" + root.getMd5() + "\";\n" + // " public final static String CONFIG_DEF_NAME = \"" + root.getName() + "\";\n" + // " public final static String CONFIG_DEF_NAMESPACE = \"" + root.getNamespace() + "\";\n" + // - " public final static String CONFIG_DEF_VERSION = \"" + root.getVersion() + "\";\n" + // + " public final static String CONFIG_DEF_VERSION = \"" + root.getVersion() + "\";\n" + // TODO: Remove on Vespa 8 " public final static String[] CONFIG_DEF_SCHEMA = {\n" + // "" + indentCode(INDENTATION + INDENTATION, getDefSchema()) + "\n" + // " };\n" + // diff --git a/configgen/src/main/java/com/yahoo/config/codegen/MakeConfig.java b/configgen/src/main/java/com/yahoo/config/codegen/MakeConfig.java index ac6bbea617e..9a7800988dc 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/MakeConfig.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/MakeConfig.java @@ -18,13 +18,14 @@ public class MakeConfig { classBuilder = createClassBuilder(root, nd, properties); } - public static ClassBuilder createClassBuilder(InnerCNode root, NormalizedDefinition nd, MakeConfigProperties properties) { + private static ClassBuilder createClassBuilder(InnerCNode root, NormalizedDefinition nd, MakeConfigProperties properties) { if (isCpp(properties)) return new CppClassBuilder(root, nd, properties.destDir, properties.dirInRoot); else return new JavaClassBuilder(root, nd, properties.destDir, properties.javaPackagePrefix); } + @SuppressWarnings("WeakerAccess") // Used by ConfigGenMojo public static boolean makeConfig(MakeConfigProperties properties) throws FileNotFoundException { for (File specFile : properties.specFiles) { String name = specFile.getName(); @@ -49,7 +50,7 @@ public class MakeConfig { /** * Generates the code and print it to this.out. */ - void buildClasses() { + private void buildClasses() { classBuilder.createConfigClasses(); } @@ -58,7 +59,7 @@ public class MakeConfig { out.println(" (default language for generated code is Java)"); } - public static void main(String[] args) throws IOException, InterruptedException { + public static void main(String[] args) throws IOException { try { MakeConfigProperties props = new MakeConfigProperties(); boolean success = makeConfig(props); @@ -81,7 +82,7 @@ public class MakeConfig { } private static boolean isCpp(MakeConfigProperties properties) { - return (properties.language.equals("cppng") || properties.language.equals("cpp")); + return properties.language.equals("cpp"); } // The Exceptions class below is copied from vespajlib/com.yahoo.protect.Exceptions @@ -100,7 +101,7 @@ public class MakeConfig { * <code>e.getMessage(): e.getCause().getMessage(): e.getCause().getCause().getMessage()...</code> * In addition, some heuristics are used to clean up common cases where exception nesting causes bad messages. */ - public static String toMessageString(Throwable t) { + static String toMessageString(Throwable t) { StringBuilder b = new StringBuilder(); String lastMessage = null; String message; diff --git a/configgen/src/main/java/com/yahoo/config/codegen/MakeConfigProperties.java b/configgen/src/main/java/com/yahoo/config/codegen/MakeConfigProperties.java index 13807e63e53..da746bf3a01 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/MakeConfigProperties.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/MakeConfigProperties.java @@ -11,9 +11,10 @@ import java.util.StringTokenizer; * * @author gjoranv */ +@SuppressWarnings("WeakerAccess") // Used by ConfigGenMojo public class MakeConfigProperties { - private static final List<String> legalLanguages = Arrays.asList("java", "cpp", "cppng" ); + private static final List<String> legalLanguages = Arrays.asList("java", "cpp" ); final File destDir; final File[] specFiles; @@ -33,13 +34,14 @@ public class MakeConfigProperties { System.getProperty("config.packagePrefix")); } + @SuppressWarnings("WeakerAccess") // Used by ConfigGenMojo public MakeConfigProperties(String destDir, - String specFiles, - String language, - String dirInRoot, - String dumpTree, - String generateFrameworkCode, - String javaPackagePrefix) throws PropertyException { + String specFiles, + String language, + String dirInRoot, + String dumpTree, + String generateFrameworkCode, + String javaPackagePrefix) throws PropertyException { this.destDir = checkDestinationDir(destDir); this.specFiles = checkSpecificationFiles(specFiles); this.language = checkLanguage(language); diff --git a/configgen/src/main/resources/make-config-preproc.pl b/configgen/src/main/resources/make-config-preproc.pl deleted file mode 100755 index a3432957e04..00000000000 --- a/configgen/src/main/resources/make-config-preproc.pl +++ /dev/null @@ -1,952 +0,0 @@ -#!/usr/bin/perl -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -# This is the config pre-processor. -# It handles import statements, and does syntax checking etc. -# The idea is that it will be called directly from the script -# that does the code generation. -# -# Errors and warnings are printed in "next-error" compatible ways -# for emacs etc. -# -# Indented like this: -# (cperl-set-style "Whitesmith") -# (setq cperl-continued-brace-offset -4) - -require 5.006_001; -use strict; -use warnings; -use Digest::MD5; - -use Math::BigInt; -use Math::BigFloat; - -die "Usage: $0 <def-file>" unless $#ARGV == 0; - -my $defname = $ARGV[0]; - -my $md5 = Digest::MD5->new; - -my @c_keywords = - ("asm", "auto", "bool", "break", "case", "catch", - "char", "class", "const", "const_cast", "continue", "default", - "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", - "export", "extern", "false", "float", "for", "friend", "goto", "if", - "inline", "int", "long", "mutable", "namespace", "new", "operator", - "private", "protected", "public", "register", "reinterpret_cast", - "return", "short", "signed", "sizeof", "static", "static_cast", - "struct", "switch", "template", "this", "throw", "true", "try", - "typedef", "typeid", "typename", "union", "unsigned", - "using", "virtual", "void", "volatile", "wchar_t", "while", "and", "bitor", - "not", "or", "xor", "and_eq", "compl", "not_eq", "or_eq", "xor_eq", - "bitand"); - - -my @java_keywords = - ("abstract", "boolean", "break", "byte", "case", - "catch", "char", "class","continue", "default", "do", "double", - "else", "extends","false", "final", "finally", "float", "for", - "if","implements", "import", "instanceof", "int", "interface", - "long","native", "new", "null", "package", "private", - "protected","public", "return", "short", "static", - "strictfp","super","switch", "synchronized", "this", - "throw","throws","transient", "true", "try", "void", - "volatile","while", "byvalue", "cast", "const", "future", - "generic","goto", "inner", "operator", "outer", "rest", "var"); - -my %reserved_words; - -foreach my $word (@c_keywords) { - $reserved_words{$word} = "C"; -} - -foreach my $word (@java_keywords) { - my $x = $reserved_words{$word}; - if (defined($x)) { - $x = "$x, Java"; - } else { - $x = "Java"; - } - $reserved_words{$word} = $x; -} - -my $MIN_INT = -0x80000000; -my $MAX_INT = 0x7fffffff; -my $MIN_DOUBLE = -1e308; -my $MAX_DOUBLE = 1e308; - - -sub do_file { - my ($file, $prefix, $strip) = @_; - - local *FH; - open FH, "< $file" or die "Cannot open $file: $!\n"; - - local *COPY; - my $dir = $ENV{"VESPA_CONFIG_DEF_DIR"}; - my $copy; - my $file_version; - if (defined($dir)) { - $copy = $file; - $copy =~ s=.*/==; - $copy = "$dir/$copy"; - open COPY, ">$copy.new" or die "Cannot open file $copy.new: $!\n"; - } - - # Read line by line. - # 1. Strip away comments and trailing blanks - # 2. Report any errors - # 3. Handle import statements, disallow multi-level imports - # 4. Print everyting to stdout - - my $linenr = 0; - my $written_lines = 0; - my $quoted_strip = quotemeta($strip); - my $seen_version = 0; - - while (<FH>) { - print COPY $_ if $copy; - ++$linenr; - my $line = $_; - chomp $line; - - # Don't process comments or add them to md5 checksum, but print them - # such that codegen can include comments - if ($line =~ /^\s*#/) { - print "$line\n"; - next; - } - - # Strip away comments that are not at start of line - $line = &strip_trailing_comment($line, $linenr) - if ($line =~ m=[\\\#]=); - - if ($line eq "::error::") { - return -1; - } - - # Skip lines that are only whitespace - next if $line =~ m=^\s*$=; - - # Get rid of trailing whitespace - $line =~ s=\s+$==; - - if (!$seen_version) { - if ($line =~ m!^version=([a-zA-Z0-9][-a-zA-Z0-9_/?]*)!) { - $file_version = $1; - $seen_version = 1; - if ($prefix) { - print "$prefix imported $file"; - print ":$strip" if $strip; - print " "; - } - print "$line\n"; - next; - } else { - print STDERR "$file:$linenr: error: Definition file does not " - . "start with a valid version= identifier!\n"; - return -1; - } - } - - if ($strip) { - next unless $line =~ m=^${quoted_strip}[. \t]=; - } - - if (&check_syntax($line, $linenr, $file) == -1) { - return -1; - } - - # Handle import statements - my ($name, $type, $remains, $junk) = split(/\s+/, $line, 4); - if ($type eq "import") { - if ($strip || $prefix) { - my $col = index($line, $type, length("$name ")); - print STDERR "$file:$linenr:$col: error: Multi-level " - . "imports are disallowed.\n"; - return -1; - } - if ($junk) { - my $col = index($line, $junk, length("$name $type $remains")) - + 1; - print STDERR "$file:$linenr:$col: error: Junk after import " - . "target \"$remains\": \"$junk\"\n"; - return -1; - } - my ($impfile, $var) = split(/:/, $remains, 2); - $var = "" unless $var; # Make it defined. - - # Make sure only arrays can include arrays: - if ($name =~ m=\[\]$= && (!$var || $var !~ m=\[\]$=)) { - print STDERR "$file:$linenr: error: Array cannot import " - . "non-array in: $line\n"; - return -1; - } elsif ($name !~ m=\[\]$= && ($var && $var =~ m=\[\]$=)) { - print STDERR "$file:$linenr: error: Non-array cannot import " - . "array in: $line\n"; - return -1; - } - - local *X; - unless (open(X, "< $impfile")) { - my $col = index($line, $remains, length("$name $type")) + 1; - print STDERR "$file:$linenr:$col: error: Cannot open " - . "\"$impfile\": $!\n"; - return -1; - } - close X; - my $imported_lines = &do_file("$impfile", "$name", "$var"); - if ($imported_lines == -1) { - my $col = index($line, $remains, length("$name $type")) + 1; - print STDERR "$file:$linenr:$col: error: Imported from here " - . "as: $line\n"; - return -1; - } elsif ($imported_lines == 0) { - my $col = index($line, $remains, length("$name $type")) + 1; - print STDERR "$file:$linenr:$col: error: Import target " - . "\"$var\" not found in \"$impfile\"\n"; - return -1; - } - $written_lines += $imported_lines; - } else { - ++$written_lines; - if ($strip) { - $line =~ s=^${quoted_strip}=${prefix}= - } elsif ($prefix) { - $line = $prefix . "." . $line; - } - - if (&check_name_sanity($line, $linenr, $file) == -1 - || &check_enum_sanity($line, $linenr, $file) == -1) { - return -1; - } - - $line = &normalize_line($line, $linenr); - if ($line eq "::error::") { - return -1; - } - print $line . "\n"; - } - # Add this line to the md5 checksum - $md5->add("$line\n") unless $prefix; - } - - print "md5=" . $md5->hexdigest . "\n" unless $prefix; - close FH; - if ($copy) { - close COPY; - # We have made a copy. It needs a new name.. - my $new_name = $copy; - $new_name =~ s=\.def==; - $new_name .= ".${file_version}.def"; - if (-f $new_name) { - system "cmp $copy.new $new_name 2>/dev/null" and die "$file:1: error: Definition file $file differs from ${new_name}!\n"; - unlink("$copy.new"); - } else { - rename("$copy.new", "$new_name") or die "Rename $copy.new -> $new_name failed: $!\n"; - } - } - return $written_lines; -} - -sub normalize_enum { - my($x, $linenr, $colnr) = @_; - my $len = length($x); - my $char = ''; - my $output = '{ '; - my $index; - my %enum = (); - my $current_variable = ''; - for ($index = $colnr + 1; $index < $len; ++$index) { - $char = substr($x, $index, 1); - if ($char eq '}') { - if (length($current_variable) < 2) { - print STDERR "$defname:$linenr:$index: error: ". - " variable must be at least two characters: $x\n" ; - return ('', 0); - } elsif ($enum{$current_variable}) { - print STDERR "$defname:$linenr:$index: error: ". - " enum variable declared twice: $x\n" ; - return ('', 0); - } elsif (!%enum && !$current_variable) { - print STDERR "$defname:$linenr:$index: error: ". - " enum cannot be empty: $x\n" ; - return ('', 0); - } - return ($output.$current_variable." } ", $index); - } elsif ($char eq ',') { - if (length($current_variable) < 2) { - print STDERR "$defname:$linenr:$index: error: ". - " variable must be at least two characters: $x\n" ; - return ('', 0); - } elsif ($enum{$current_variable}) { - print STDERR "$defname:$linenr:$index: error: ". - " enum variable declared twice: $x\n" ; - return ('', 0); - } - $enum{$current_variable} = 1; - $output .= "$current_variable, "; - $current_variable = ''; - } elsif ($char =~ m=[A-Z]=) { - $current_variable .= $char; - } elsif ($char =~ m=[0-9_]= && $current_variable) { - $current_variable .= $char; - } elsif ($char =~ m=\s=) { - if ($current_variable && !($x =~ /^.{$index}\s*[,\}]/)) { - print STDERR "$defname:$linenr:$index: error: ". - "expected ',' or '}': $x\n" ; - return ("", 0); - } else { - # skip whitespace - } - } else { - print STDERR "<$char> <$current_variable>\n"; - - print STDERR "$defname:$linenr:$index: error: ". - "Enum must match [A-Z][A-Z0-9_]+: $x\n"; - } - } - return ($output, $index); -} - -{ package Range; - - $Range::DOUBLE_RANGE = - new Range("a double range=[$MIN_DOUBLE,$MAX_DOUBLE] ",0,14); - $Range::INT_RANGE = new Range("a int range=[$MIN_INT,$MAX_INT] ",0,11); - - - sub in_range { - my($self, $value) = @_; - - if ($value =~ s/KB$//) { - $value *= 1024; - } elsif ($value =~ s/MB$//) { - $value *= (1024 * 1024); - } elsif ($value =~ s/GB$//) { - $value *= (1024*1024*1024); - } elsif ($value =~ s/k$//) { - $value *= 1000; - } elsif ($value =~ s/M$//) { - $value *= 1_000_000; - } elsif ($value =~ s/G$//) { - $value *= 1_000_000_000; - } elsif ($value =~ m=^0[xX]=) { - $value = hex($value); - } - - if ($self->{start_bracket} eq '(' ) { - return 0 if $value <= $self->{min}; - } elsif ($self->{start_bracket} eq '[' ) { - return 0 if $value < $self->{min}; - } else { - print STDERR "Illegal start_bracket '$self->{start_bracket}'\n"; - return undef; - } - if ($self->{end_bracket} eq ')' ) { - return 0 if $value >= $self->{max}; - } elsif ($self->{end_bracket} eq ']' ) { - return 0 if $value > $self->{max}; - } else { - print STDERR "Illegal end_bracket '$self->{start_bracket}'\n"; - return undef; - } - return 1; - } - - - sub new { - my($class, $x, $linenr, $colnr) = @_; - my $len = length($x); - my $self = {}; - bless($self, $class); - $self->{min_value} = ''; - my $index; - for ($index = $colnr + 1; $index < $len; ++$index) { - my $char = substr($x, $index, 1); - if (($char eq '(' || $char eq '[') && !$self->{start_bracket}) { - $self->{start_bracket} = $char; - } elsif (($char eq ')' || $char eq ']') && !$self->{end_bracket}) { - $self->{end_bracket} = $char; - last; - } elsif ($char =~ m=\s=) { - #ignore whitespace - } elsif ($char eq ',' && !defined($self->{max_value})) { - $self->{max_value} = ''; - } elsif ($char =~ m=[\d\.\+eE-]= ) { - (defined($self->{max_value}) - ? $self->{max_value} : $self->{min_value}) .= $char; - } else { - print STDERR "$defname:$linenr:$index: error: ". - " syntax error: $x\n" ; - return undef; - } - } - if ($self->{min_value} eq '' && $self->{max_value} eq '') { - print STDERR "$defname:$linenr:$colnr: error: ". - " range cannot be unbounded in both ends: $x\n" ; - return undef; - } - unless ($self->{start_bracket} && $self->{end_bracket}) { - print STDERR "$defname:$linenr:$colnr: error: ". - " missing bracket: $x\n" ; - return undef; - } - - - my @arr = split(/\s+/, $x, 3); - if ($arr[1] eq 'int') { - $self->{min} = Math::BigInt->new - ($self->{min_value} eq '' ? $MIN_INT : $self->{min_value}); - unless (defined($self->{min}) && $self->{min} ne 'NaN') { - print STDERR "$defname:$linenr:$colnr: error: ". - " parse error $self->{min_value}: $x\n" ; - return undef; - } - my $min_val = - $self->{min} + ($self->{start_bracket} eq '('? 1 : 0); - - $self->{max} = Math::BigInt->new - ($self->{max_value} eq '' ? $MAX_INT : $self->{max_value}); - unless (defined($self->{max}) && $self->{max} ne 'NaN') { - print STDERR "$defname:$linenr:$colnr: error: ". - " parse error $self->{max_value}: $x\n" ; - return undef; - } - my $max_val = - $self->{max} - ($self->{end_bracket} eq ')'? 1 : 0); - - if ($min_val < $MIN_INT ) { - print STDERR "$defname:$linenr:$colnr: error: ". - " start of interval less than MIN_INT: $x\n" ; - return undef; - } - if ($max_val > $MAX_INT) { - print STDERR "$self->{max} - 1 > $MAX_INT\n"; - print STDERR "$defname:$linenr:$colnr: error: ". - " end of interval greater than MAX_INT: $x\n" ; - return undef; - } - if ($max_val < $min_val) { - print STDERR "$defname:$linenr:$colnr: error: ". - " illegal range: $x\n" ; - return undef; - } - $self->{string} = - "$self->{start_bracket}$self->{min},$self->{max}$self->{end_bracket}"; - $self->{string} =~ s/\+//g; - $self->{index} = $index; - return $self; - } elsif ($arr[1] eq 'double') { - $self->{min} = Math::BigFloat->new - ($self->{min_value} eq '' ? $MIN_DOUBLE : $self->{min_value}); - unless (defined($self->{min}) && $self->{min} ne 'NaN') { - print STDERR "$defname:$linenr:$colnr: error: ". - " parse error $self->{min_value}: $x\n" ; - return undef; - } - $self->{max} = Math::BigFloat->new - ($self->{max_value} eq '' ? $MAX_DOUBLE : $self->{max_value}); - unless (defined($self->{max}) && $self->{max} ne 'NaN') { - print STDERR "$defname:$linenr:$colnr: error: ". - " parse error $self->{max_value}: $x\n" ; - return undef; - } - if ($self->{min} < $MIN_DOUBLE) { - print STDERR "$defname:$linenr:$colnr: error: ". - " start of interval less than MIN_DOUBLE: $x\n" ; - return undef; - } - if ($self->{max} > $MAX_DOUBLE) { - print STDERR "$defname:$linenr:$colnr: error: ". - " start of interval greater than MAX_DOUBLE: $x\n" ; - return undef; - } - if ($self->{max} < $self->{min}) { - print STDERR "$defname:$linenr:$colnr: error: ". - " illegal range: $x\n" ; - return undef; - } - if (($self->{start_bracket} eq '(' || $self->{end_bracket} eq ')') - && ($self->{min_value} + $self->{min_value} - >= $self->{min_value} + $self->{max_value}) - && ($self->{max_value} + $self->{max_value} - <= $self->{min_value} + $self->{max_value})) { - print STDERR "$defname:$linenr:$colnr: error: ". - " illegal range: $x\n" ; - return undef; - } - $self->{string} = $self->{start_bracket}.$self->{min}->fnorm. - ','.$self->{max}->fnorm.$self->{end_bracket}; - $self->{string} =~ s/\+//g; - $self->{index} = $index; - return $self; - } else { - print STDERR "$defname:$linenr:$colnr: error: ". - " range-option works only for type 'int' and 'double': $x\n" ; - return undef; - } - print STDERR "$defname:$linenr:$colnr: error: ". - " script error: $x\n" ; - return undef; - - } - -} - - - - -sub strip_trailing_comment { - my ($x, $linenr) = @_; - - my $index = 0; - my $len = length($x); - my $in_quotes = 0; - - # ### Support both " and ' quotes maybe? - - for ($index = 0; $index < $len; ++$index) { - if (substr($x, $index, 1) eq "\\") { - ++$index; - next; - } - if (substr($x, $index, 1) eq "\"") { - $in_quotes ^= 1; - } - if ($in_quotes == 0 && substr($x, $index, 1) eq "#") { - if (!(substr($x, $index - 1, 1) =~ m=\s=)) { - my $col = $index + 1; - print STDERR "$defname:$linenr:$col: warning: No whitespace " - . "before comment in line: $x\n"; - } - print substr($x, $index). "\n"; - $x = substr($x, 0, $index); - last; - } - } - if ($index > $len) { - print STDERR "$defname:$linenr:$len: error: syntax error, line " - . "ends with \\: \"$x\"\n"; - return "::error::"; - } - - return $x; -} - -sub normalize_line { - my ($x, $linenr) = @_; - - my $index = 0; - my $len = length($x); - my $in_quotes = 0; - my $char = ''; - my $output = ''; - my %hash = (); - - my @arr = split(/\s+/, $x, 3); - $hash{type} = $arr[1]; - - for ($index = 0; $index < length($x); ++$index) { - $char = substr($x, $index, 1); - if ($char eq "\\") { - $output .= substr($x, $index, 2); - ++$index; - next; - } - if ($char eq "\"") { - $in_quotes ^= 1; - $output .= $char; - next; - } - my $ends_with_whitespace = ($output =~ m= $=); - - if ($in_quotes == 0) { - if ($char =~ m=\s=) { - #delete multiple spaces - if (!$ends_with_whitespace) { # && ($output =~ !m=\=$=)) { - $output .= ' '; - } - } elsif ($char eq '{') { - my($enum, $i) = &normalize_enum($x, $linenr, $index); - return "::error::" unless $i; - $index = $i; - $output .= ($ends_with_whitespace) ? $enum : " $enum "; - } elsif ($char eq ',') { - chop $output if ($ends_with_whitespace); - $output .= ','; - } elsif ($char eq '=') { - chop $output if ($ends_with_whitespace); - $output .= '='; - if ($output =~ /range=$/) { - $hash{range} = - new Range($x, $linenr, $index); - return "::error::" unless $hash{range}; - $index = $hash{range}->{index}; - $output .= $hash{range}->{string}." "; - } - if ($output =~ /default=$/ - && ($hash{type} eq 'int' || $hash{type} eq 'double')) { - $x =~ /^.{$index}=\s*(\S+)/; - $hash{default} = $1; - if ($hash{type} eq 'int' && - !$Range::INT_RANGE->in_range($hash{default})) { - print STDERR "$defname:$linenr:$index: error: ". - "Default not in range: $x\n"; - return "::error::"; - } - if ($hash{type} eq 'double' && - !$Range::DOUBLE_RANGE->in_range($hash{default})) { - print STDERR "$defname:$linenr:$index: error: ". - "Default not in range: $x\n"; - return "::error::"; - } - } - if (defined($hash{default}) && $hash{range}) { - unless ($hash{range}->in_range($hash{default})) { - print STDERR "$defname:$linenr:$index: error: ". - "Default not in range: $x\n"; - return "::error::"; - } - } - } else { - $output .= $char; - } - } else { - $output .= $char; - } - } - if ($index > $len) { - print STDERR "$defname:$linenr:$len: error: syntax error, line " - . "ends with \\: \"$x\"\n"; - return "::error::"; - } - chop $output if $output =~ m/ $/; - return $output; -} - -my %used_enum; -sub check_enum_sanity { - my ($line, $linenr, $file) = @_; - - my ($name, $type, $rest) = split(/\s+/, $line, 3); - return 0 unless ($type eq "enum"); - - $name =~ /(.*)\./; - my $prefix = $1; - $prefix = "" unless defined $prefix; # Make top level prefix - $used_enum{"$prefix"} = $used_enum{"$prefix"} || {}; - $rest = "" unless defined $rest; - $rest =~ /\{\s*(.*?)\}/; - my @values = split(/[,\s]+/, $1); - foreach my $value (@values) { - if ($used_enum{"$prefix"}->{$value}) { - print STDERR - "$file:$linenr: error: Name \"$value\" is already defined\n"; - my $prevdef = $used_enum{"$prefix"}->{$value}; - print STDERR "$prevdef: error: At this point\n"; - return -1; - } else { - $used_enum{"$prefix"}->{$value} = "$file:$linenr"; - } - } - return 0; -} - - -my %used_name; -my %used_component; -my %banned_prefixes; -my $cns_prev_name; -sub check_name_sanity { - my ($line, $linenr, $file) = @_; - my ($name, $junk) = split(/\s+/, $line, 2); - - my $plain_name = $name; - $plain_name =~ s=\[\]$==; - - # See if the name is already used. - if ($used_name{"$plain_name"}) { - print STDERR - "$file:$linenr: error: Name \"$name\" is already defined\n"; - my $prevdef = $used_name{$name}; - print STDERR "$prevdef: error: At this point\n"; - return -1; - } else { - $used_name{$name} = "$file:$linenr"; - } - - # Test for bans - my $banned = "${name}."; - do { - my $err = $banned_prefixes{$banned}; - if (defined($err)) { - print STDERR "$file:$linenr: error: The prefix \"$banned\" is illegal here\n"; - print STDERR "$err\n"; - return -1; - } - } while (($banned =~ s=[.][^.]+[.]$=.=)); - - # Add any new bans generated by this line - $banned_prefixes{"${name}."} = "$file:$linenr: error: \"${name}\" cannot " - . "be both a struct and a non-struct!"; - if ($cns_prev_name) { - my $prev = $cns_prev_name; - my $oldprev = $prev; - while (($prev =~ s=[.][^.]+[.]?$=.=)) { - if (substr($name, 0, length($prev)) eq $prev) { - $banned_prefixes{"$oldprev"} = "$file:" . ($linenr - 1) - . ": error: Last possible line is after this"; - last; - } - $oldprev = $prev; - } - } - $cns_prev_name = $name; - - # See if any of the components previously have a different "arrayness" - my $part_name = $name; - while (($part_name =~ s=[.][^.]+$==)) { - my $clashing_name = $part_name; - if ($part_name =~ m=\[\]$=) { - $clashing_name =~ s=\[\]$==; - } else { - $clashing_name .= "[]"; - } - my $clashline = $used_component{"$clashing_name"}; - if (defined $clashline) { - print STDERR "$file:$linenr: error: \"$clashing_name\" cannot be both array and non-array\n"; - print STDERR "$clashline: error: Previously defined here\n"; - return -1; - } elsif (!$used_component{"$part_name"}) { - $used_component{"$part_name"} = "$file:$linenr"; - } - } - return 0; -} - -# These are all the allowed types/commands -my %types = ( "int" => \&check_int, - "double" => \&check_double, - "string" => \&check_string, - "reference" => \&check_reference, - "enum" => \&check_enum, - "bool" => \&check_bool, - "properties" => \&check_properties, - "import" => \&check_import ); - -sub check_syntax { - my ($line, $linenr, $file) = @_; - - my $col = 0; - my $llen = length($line); - - # Step 1. Sanity check the name. - my $atstart = 1; - my $array_ok = 1; - - for ($col = 0; $col < $llen; ++$col) { - my $c = substr($line, $col, 1); - if ($atstart) { - if ($c !~ m=[a-zA-Z]=) { - print STDERR "$file:$linenr:$col: error: Non-alphabetic start " - . "of variable name in $line\n"; - return -1; - } - $atstart = 0; - } else { - if ($c =~ m=[a-zA-Z0-9_]=) { - 0; # Do nothing - } elsif ($c eq ".") { - $atstart = 1; - $array_ok = 1; - } elsif ($c eq "[") { - if (!$array_ok) { - ++$col; - print STDERR "$file:$linenr:$col: error: Arrays cannot be " - . "multidimensional in $line\n"; - return -1; - } - ++$col; - $array_ok = 0; - $c = substr($line, $col, 1); - if ($c ne "]") { - ++$col; - print STDERR "$file:$linenr:$col: error: Expected ] to " - . "terminate array definition in $line\n"; - return -1; - } - } elsif ($c =~ m=\s=) { - last; - } else { - ++$col; - print STDERR "$file:$linenr:$col: error: Syntax error, " - . "unexpected character in $line\n"; - return -1; - } - } - } - - my $name = substr($line, 0, $col); - $name =~ s=.*[.]==; - $name =~ s=[[]]$==; - - my $clash = $reserved_words{$name}; - if ($clash) { - $col -= (3 + length($name)); - $col = index($line, $name, $col) + 1; - print STDERR "$file:$linenr:$col: error: $name is a reserved word in: " - . "${clash}\n"; - return -1; - } - - while (substr($line, $col, 1) =~ m=\s=) { - ++$col; - } - - # At this point the name is sane. Next, check the type. - my ($type) = split(/\s/, substr($line, $col)); - - unless (defined $types{$type}) { - ++$col; - print STDERR "$file:$linenr:$col: error: Unknown type/command " - . "\"$type\"\n"; - return -1; - } - $col += length($type); - while (substr($line, $col, 1) =~ m=\s=) { - ++$col; - } - return $types{$type}($col, $line, $linenr, $file); -} - -sub reg_words_check { - my ($col, $line, $linenr, $file, $reg) = @_; - my $remainder = substr($line, $col); - my @options = split(/\s+/, $remainder); - - foreach my $option (@options) { - # Keep track of where we are for error reporting - $col = index($line, $option, $col) + 1; - unless ($option =~ m!${reg}!) { - print STDERR "$file:$linenr:$col: error: Bad option \"$option\" no match for m!${reg}!\n"; - return -1; - } - } - return 0; -} - -sub check_int { - my ($col, $line, $linenr, $file) = @_; - my $num = "(-?\\d+(KB|MB|GB|k|M|G)?|0x[0-9a-fA-F]+)"; # All legal numbers - my $optnum = "(${num})?"; # All legal optional numbers - return ®_words_check($col, $line, $linenr, $file, - "^(" - . "default=${num}" - . "|range=[[(]${optnum},${optnum}"."[])]" - . "|restart" - . ")\$"); -} - -sub check_double { - my ($col, $line, $linenr, $file) = @_; - my $num = "-?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?"; # All legal doubles - my $optnum = "(${num})?"; # Optional doubles - return ®_words_check($col, $line, $linenr, $file, - "^(" - . "default=${num}" - . "|range=[[(]${optnum},${optnum}"."[])]" - . "|restart" - . ")\$"); -} - -sub check_string { - my ($col, $line, $linenr, $file) = @_; - my $opts = substr($line, $col); - - # not entirely correct either for something like \\" - my $def = "default=((\"(\\\"|[^\"])*\")|null)"; - - my $res = "restart"; - my $reg = "^(${def}\\s+${res}|(${def})?|${res}|${res}\\s+${def})\$"; - - unless ($opts =~ m!${reg}!) { - print STDERR "$file:$linenr:$col: error: Bad options \"$opts\", no match for m!${reg}!\n"; - return -1; - } - return 0; -} - -sub check_reference { - my ($col, $line, $linenr, $file) = @_; - my $opts = substr($line, $col); - my $def = "default=((\"(\\\"|[^\"])*\")|null)"; - - unless ($opts eq "" || $opts =~m!${def}!) { - print STDERR "$file:$linenr:$col: error: reference can only " - . "take the 'default' option\n"; - return -1; - } - return 0; -} - - -sub check_enum { - my ($col, $line, $linenr, $file) = @_; - my $ret = ®_words_check($col, $line, $linenr, $file, - "^(" - . "[{},]" - . "|[A-Z][A-Z0-9_]+,?" - . "|default=[A-Z][A-Z0-9_]+" - . "|restart" - . ")\$"); - return -1 if $ret; - $col = index($line, '}', $col) + 1; #move $col to end of enum --> } - while (substr($line, $col, 1) =~ m=[\s\{]=) { - ++$col; - } - return 0 if $col >= length($line); - - - return ®_words_check($col, $line, $linenr, $file, - "^(" - . "default=[A-Z][A-Z0-9_]+" - . "|restart" - . ")\$"); -} - -sub check_bool { - my ($col, $line, $linenr, $file) = @_; - return ®_words_check($col, $line, $linenr, $file, - "^(" - . "default=(true|false)" - . "|restart" - . ")\$"); -} - -sub check_properties { - my ($col, $line, $linenr, $file) = @_; - return ®_words_check($col, $line, $linenr, $file, "^restart\$"); -} - -sub check_import { - my ($col, $line, $linenr, $file) = @_; - my $word = "[a-zA-Z][_a-zA-Z0-9]*"; - my $fnam = "${word}(\\.${word})*"; - my $var = "${word}((\\[\\])?\.${word})*(\\[\\])?"; - return ®_words_check($col, $line, $linenr, $file, - "^${fnam}\\.def:(${var})?\$"); - return 0; -} - - -my $lines = &do_file($defname, "", ""); - -if ($lines == -1) { - die "There were irrecoverable errors in \"$defname\"!\n"; -} -if ($lines == 0) { - die "$defname:1: error: Resulting definition is empty!\n"; -} - -exit 0; diff --git a/configgen/src/test/java/com/yahoo/config/codegen/MakeConfigTest.java b/configgen/src/test/java/com/yahoo/config/codegen/MakeConfigTest.java index 501d7778fd7..7486d464e43 100644 --- a/configgen/src/test/java/com/yahoo/config/codegen/MakeConfigTest.java +++ b/configgen/src/test/java/com/yahoo/config/codegen/MakeConfigTest.java @@ -12,7 +12,7 @@ import org.junit.Test; public class MakeConfigTest { - File dest; + private File dest; @Before public void setUp() { @@ -29,8 +29,9 @@ public class MakeConfigTest { if (dir.isDirectory()) { String[] children = dir.list(); - for (int i = 0; i < children.length; i++) { - boolean success = recursiveDeleteDir(new File(dir, children[i])); + assert children != null; + for (String child : children) { + boolean success = recursiveDeleteDir(new File(dir, child)); if (!success) return false; } @@ -42,10 +43,8 @@ public class MakeConfigTest { @Test public void testProps() throws PropertyException { - long ts = System.currentTimeMillis(); System.setProperty("config.dumpTree", "true"); System.setProperty("config.useFramework", "true"); - System.setProperty("config.requireNamespace", "true"); System.setProperty("config.dest", dest.getAbsolutePath()); System.setProperty("config.spec", "src/test/resources/allfeatures.def"); MakeConfigProperties p = new MakeConfigProperties(); @@ -57,7 +56,6 @@ public class MakeConfigTest { System.setProperty("config.dumpTree", "false"); System.setProperty("config.useFramework", "false"); - System.setProperty("config.requireNamespace", "false"); System.setProperty("config.dest", dest.getAbsolutePath()); System.setProperty("config.spec", "src/test/resources/allfeatures.def,src/test/resources/bar.foo.def"); p = new MakeConfigProperties(); @@ -71,7 +69,6 @@ public class MakeConfigTest { public void testMake() throws IOException, InterruptedException { System.setProperty("config.dumpTree", "true"); System.setProperty("config.useFramework", "true"); - System.setProperty("config.requireNamespace", "true"); System.setProperty("config.dest", dest.getAbsolutePath()); System.setProperty("config.spec", "src/test/resources/allfeatures.def"); MakeConfig.main(new String[]{}); diff --git a/configserver-flags/pom.xml b/configserver-flags/pom.xml index 8c96512c4c0..11ef9b6c950 100644 --- a/configserver-flags/pom.xml +++ b/configserver-flags/pom.xml @@ -20,6 +20,12 @@ <!-- provided --> <dependency> <groupId>com.yahoo.vespa</groupId> + <artifactId>container-dev</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> <artifactId>zkfacade</artifactId> <version>${project.version}</version> <scope>provided</scope> diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java index 92397fc84a7..c706a2b1e51 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java @@ -1,12 +1,11 @@ // Copyright 2019 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.FlagDefinition; import com.yahoo.vespa.flags.json.DimensionHelper; @@ -42,6 +41,7 @@ public class DefinedFlag extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlags.java index 9604c51ee4b..26d590593c0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlags.java @@ -1,11 +1,10 @@ // Copyright 2018 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.FlagDefinition; import java.io.IOException; @@ -18,8 +17,7 @@ import java.util.List; */ public class DefinedFlags extends HttpResponse { private static ObjectMapper mapper = new ObjectMapper(); - private static final Comparator<FlagDefinition> sortByFlagId = - (left, right) -> left.getUnboundFlag().id().compareTo(right.getUnboundFlag().id()); + private static final Comparator<FlagDefinition> sortByFlagId = Comparator.comparing(flagDefinition -> flagDefinition.getUnboundFlag().id()); private final List<FlagDefinition> flags; @@ -40,6 +38,6 @@ public class DefinedFlags extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } } diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/ErrorResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/ErrorResponse.java new file mode 100644 index 00000000000..b9e5c75fe22 --- /dev/null +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/ErrorResponse.java @@ -0,0 +1,66 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.configserver.flags.http; + +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; + +import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; +import static com.yahoo.jdisc.Response.Status.FORBIDDEN; +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.NOT_FOUND; +import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED; + +/** + * A HTTP JSON response containing an error code and a message + * + * @author bratseth + */ +public class ErrorResponse extends SlimeJsonResponse { + + public enum errorCodes { + NOT_FOUND, + BAD_REQUEST, + FORBIDDEN, + METHOD_NOT_ALLOWED, + INTERNAL_SERVER_ERROR, + UNAUTHORIZED + } + + public ErrorResponse(int statusCode, String errorType, String message) { + super(statusCode, asSlimeMessage(errorType, message)); + } + + private static Slime asSlimeMessage(String errorType, String message) { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + root.setString("error-code", errorType); + root.setString("message", message); + return slime; + } + + public static ErrorResponse notFoundError(String message) { + return new ErrorResponse(NOT_FOUND, errorCodes.NOT_FOUND.name(), message); + } + + public static ErrorResponse internalServerError(String message) { + return new ErrorResponse(INTERNAL_SERVER_ERROR, errorCodes.INTERNAL_SERVER_ERROR.name(), message); + } + + public static ErrorResponse badRequest(String message) { + return new ErrorResponse(BAD_REQUEST, errorCodes.BAD_REQUEST.name(), message); + } + + public static ErrorResponse forbidden(String message) { + return new ErrorResponse(FORBIDDEN, errorCodes.FORBIDDEN.name(), message); + } + + public static ErrorResponse unauthorized(String message) { + return new ErrorResponse(UNAUTHORIZED, errorCodes.UNAUTHORIZED.name(), message); + } + + public static ErrorResponse methodNotAllowed(String message) { + return new ErrorResponse(METHOD_NOT_ALLOWED, errorCodes.METHOD_NOT_ALLOWED.name(), message); + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataListResponse.java index b33fc7c2b04..efc78cb7930 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataListResponse.java @@ -1,12 +1,11 @@ // Copyright 2018 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.json.FlagData; import com.yahoo.vespa.flags.json.wire.WireFlagDataList; @@ -54,6 +53,6 @@ public class FlagDataListResponse extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataResponse.java index 054b218ff2d..8ff4085df8d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataResponse.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataResponse.java @@ -1,9 +1,8 @@ // Copyright 2018 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.json.FlagData; import java.io.OutputStream; @@ -26,6 +25,6 @@ public class FlagDataResponse extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java index 00f3d457d3d..40bb69111e0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java @@ -1,14 +1,12 @@ // Copyright 2018 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.google.inject.Inject; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.log.LogLevel; import com.yahoo.restapi.Path; -import com.yahoo.vespa.config.server.http.HttpErrorResponse; -import com.yahoo.vespa.config.server.http.HttpHandler; -import com.yahoo.vespa.config.server.http.NotFoundException; import com.yahoo.vespa.configserver.flags.FlagsDb; import com.yahoo.vespa.flags.FlagDefinition; import com.yahoo.vespa.flags.FlagId; @@ -25,7 +23,8 @@ import java.util.Objects; * * @author hakonhall */ -public class FlagsHandler extends HttpHandler { +public class FlagsHandler extends LoggingRequestHandler { + private final FlagsDb flagsDb; @Inject @@ -35,28 +34,44 @@ public class FlagsHandler extends HttpHandler { } @Override - protected HttpResponse handleGET(HttpRequest request) { + public HttpResponse handle(HttpRequest request) { + try { + switch (request.getMethod()) { + case GET: return handleGET(request); + case DELETE: return handleDELETE(request); + case PUT: return handlePUT(request); + default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); + } + } + catch (IllegalArgumentException e) { + return ErrorResponse.badRequest(Exceptions.toMessageString(e)); + } + catch (RuntimeException e) { + log.log(LogLevel.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); + return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); + } + } + + private HttpResponse handleGET(HttpRequest request) { Path path = new Path(request.getUri()); if (path.matches("/flags/v1")) return new V1Response(flagsV1Uri(request), "data", "defined"); if (path.matches("/flags/v1/data")) return getFlagDataList(request); if (path.matches("/flags/v1/data/{flagId}")) return getFlagData(findFlagId(request, path)); if (path.matches("/flags/v1/defined")) return new DefinedFlags(Flags.getAllFlags()); if (path.matches("/flags/v1/defined/{flagId}")) return getDefinedFlag(findFlagId(request, path)); - throw new NotFoundException("Nothing at path '" + path + "'"); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); } - @Override - protected HttpResponse handlePUT(HttpRequest request) { + private HttpResponse handlePUT(HttpRequest request) { Path path = new Path(request.getUri()); if (path.matches("/flags/v1/data/{flagId}")) return putFlagData(request, findFlagId(request, path)); - throw new NotFoundException("Nothing at path '" + path + "'"); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); } - @Override - protected HttpResponse handleDELETE(HttpRequest request) { + private HttpResponse handleDELETE(HttpRequest request) { Path path = new Path(request.getUri()); if (path.matches("/flags/v1/data/{flagId}")) return deleteFlagData(findFlagId(request, path)); - throw new NotFoundException("Nothing at path '" + path + "'"); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); } private String flagsV1Uri(HttpRequest request) { @@ -66,19 +81,24 @@ public class FlagsHandler extends HttpHandler { } private HttpResponse getDefinedFlag(FlagId flagId) { - FlagDefinition definition = Flags.getFlag(flagId) - .orElseThrow(() -> new NotFoundException("Flag " + flagId + " not defined")); - return new DefinedFlag(definition); + var definedFlag = Flags.getFlag(flagId).map(DefinedFlag::new); + if (definedFlag.isPresent()) { + return definedFlag.get(); + } + return ErrorResponse.notFoundError("Flag " + flagId + " not defined"); } private HttpResponse getFlagDataList(HttpRequest request) { return new FlagDataListResponse(flagsV1Uri(request), flagsDb.getAllFlags(), - Objects.equals(request.getProperty("recursive"), "true")); + Objects.equals(request.getProperty("recursive"), "true")); } private HttpResponse getFlagData(FlagId flagId) { - FlagData data = flagsDb.getValue(flagId).orElseThrow(() -> new NotFoundException("Flag " + flagId + " not set")); - return new FlagDataResponse(data); + var data = flagsDb.getValue(flagId).map(FlagDataResponse::new); + if (data.isPresent()) { + return data.get(); + } + return ErrorResponse.notFoundError("Flag " + flagId + " not set"); } private HttpResponse putFlagData(HttpRequest request, FlagId flagId) { @@ -86,7 +106,7 @@ public class FlagsHandler extends HttpHandler { try { data = FlagData.deserialize(request.getData()); } catch (UncheckedIOException e) { - return HttpErrorResponse.badRequest("Failed to deserialize request data: " + Exceptions.toMessageString(e)); + return ErrorResponse.badRequest("Failed to deserialize request data: " + Exceptions.toMessageString(e)); } if (!isForce(request)) { @@ -105,16 +125,14 @@ public class FlagsHandler extends HttpHandler { private FlagId findFlagId(HttpRequest request, Path path) { FlagId flagId = new FlagId(path.get("flagId")); - - if (!isForce(request)) { - Flags.getFlag(flagId).orElseThrow(() -> - new NotFoundException("There is no flag '" + flagId + "' (use ?force=true to override)")); + if (!isForce(request) && Flags.getFlag(flagId).isEmpty()) { + throw new IllegalArgumentException("There is no flag '" + flagId + "' (use ?force=true to override)"); } - return flagId; } private boolean isForce(HttpRequest request) { return Objects.equals(request.getProperty("force"), "true"); } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/OKResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/OKResponse.java index 87c02ae56f1..f41940f692b 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/OKResponse.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/OKResponse.java @@ -1,9 +1,8 @@ // Copyright 2018 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.yahoo.container.jdisc.EmptyResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; /** * @author hakonhall @@ -15,6 +14,6 @@ public class OKResponse extends EmptyResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } } diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/SlimeJsonResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/SlimeJsonResponse.java new file mode 100644 index 00000000000..e5568514894 --- /dev/null +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/SlimeJsonResponse.java @@ -0,0 +1,38 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.configserver.flags.http; + +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A generic Json response using Slime for JSON encoding + * + * @author bratseth + */ +public class SlimeJsonResponse extends HttpResponse { + + private final Slime slime; + + public SlimeJsonResponse(Slime slime) { + super(200); + this.slime = slime; + } + + public SlimeJsonResponse(int statusCode, Slime slime) { + super(statusCode); + this.slime = slime; + } + + @Override + public void render(OutputStream stream) throws IOException { + new JsonFormat(true).encode(stream, slime); + } + + @Override + public String getContentType() { return "application/json"; } + +} diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/V1Response.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/V1Response.java new file mode 100644 index 00000000000..ac1e9514700 --- /dev/null +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/V1Response.java @@ -0,0 +1,46 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.configserver.flags.http; + +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.jdisc.Response; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * @author hakonhall + */ +public class V1Response extends HttpResponse { + + private final Slime slime; + + public V1Response(String flagsV1Uri, String... names) { + super(Response.Status.OK); + this.slime = generateBody(flagsV1Uri, List.of(names)); + } + + @Override + public void render(OutputStream stream) throws IOException { + new JsonFormat(true).encode(stream, slime); + } + + @Override + public String getContentType() { + return "application/json"; + } + + private static Slime generateBody(String flagsV1Uri, List<String> names) { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + names.forEach(name -> { + Cursor data = root.setObject(name); + data.setString("url", flagsV1Uri + "/" + name); + }); + return slime; + } + +} diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/package-info.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/package-info.java new file mode 100644 index 00000000000..87b63114b73 --- /dev/null +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author mpolden + */ +@ExportPackage +package com.yahoo.vespa.configserver.flags.http; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java index 97e66d95715..d6f078326a3 100644 --- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java @@ -3,5 +3,3 @@ package com.yahoo.vespa.configserver.flags; import com.yahoo.osgi.annotation.ExportPackage; - -/** The node repository controls and allocates the nodes available in a hosted Vespa zone */ diff --git a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java index d0d1d61628c..c46677bfc10 100644 --- a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java +++ b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java @@ -103,4 +103,4 @@ public class ConfigServerFlagSourceTest { assertFalse(rawFlag2.isPresent()); verify(flagsDb, times(1)).getValue(flagId2); } -}
\ No newline at end of file +} diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java index 5ae6ce9820b..cbd37c8a5cf 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java +++ b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java @@ -1,25 +1,27 @@ // Copyright 2018 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.flags; +package com.yahoo.vespa.configserver.flags.http; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.text.Utf8; -import com.yahoo.vespa.config.server.http.SessionHandlerTest; +import com.yahoo.vespa.configserver.flags.FlagsDb; import com.yahoo.vespa.configserver.flags.db.FlagsDbImpl; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.UnboundBooleanFlag; +import com.yahoo.yolean.Exceptions; import org.junit.Test; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.yahoo.yolean.Exceptions.uncheck; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -37,7 +39,7 @@ public class FlagsHandlerTest { private static final String FLAGS_V1_URL = "https://foo.com:4443/flags/v1"; - private final FlagsDbImpl flagsDb = new FlagsDbImpl(new MockCurator()); + private final FlagsDb flagsDb = new FlagsDbImpl(new MockCurator()); private final FlagsHandler handler = new FlagsHandler(FlagsHandler.testOnlyContext(), flagsDb); @Test @@ -161,7 +163,7 @@ public class FlagsHandlerTest { @Test public void testForcing() { - assertThat(handle(Method.PUT, "/data/" + new FlagId("undef"), "", 404), + assertThat(handle(Method.PUT, "/data/" + new FlagId("undef"), "", 400), containsString("There is no flag 'undef'")); assertThat(handle(Method.PUT, "/data/" + new FlagId("undef") + "?force=true", "", 400), @@ -191,10 +193,12 @@ public class FlagsHandlerTest { HttpResponse response = handler.handle(request); assertEquals(expectedStatus, response.getStatus()); assertEquals("application/json", response.getContentType()); - return uncheck(() -> SessionHandlerTest.getRenderedString(response)); + var outputStream = new ByteArrayOutputStream(); + Exceptions.uncheck(() -> response.render(outputStream)); + return outputStream.toString(StandardCharsets.UTF_8); } private InputStream makeInputStream(String content) { return new ByteArrayInputStream(Utf8.toBytes(content)); } -}
\ No newline at end of file +} diff --git a/configserver/pom.xml b/configserver/pom.xml index f346cde63a3..fd33950a546 100644 --- a/configserver/pom.xml +++ b/configserver/pom.xml @@ -185,6 +185,12 @@ <scope>compile</scope> </dependency> <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>jaxrs_client_utils</artifactId> + <version>${project.version}</version> + <scope>compile</scope> <!-- TODO Should ideally be provided, but this bundle is not installed as part of configserver. Orchestrator bundle also includes jaxrs_client_utils in compile scope --> + </dependency> + <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <scope>provided</scope> diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index 192488c11ab..62fef0ad79a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -42,7 +42,11 @@ import com.yahoo.vespa.config.server.deploy.Deployment; import com.yahoo.vespa.config.server.deploy.InfraDeployerProvider; import com.yahoo.vespa.config.server.http.LogRetriever; import com.yahoo.vespa.config.server.http.SimpleHttpFetcher; +import com.yahoo.vespa.config.server.http.v2.MetricsResponse; import com.yahoo.vespa.config.server.http.v2.PrepareResult; +import com.yahoo.vespa.config.server.metrics.ClusterInfo; +import com.yahoo.vespa.config.server.metrics.MetricsAggregator; +import com.yahoo.vespa.config.server.metrics.MetricsRetriever; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.session.LocalSession; import com.yahoo.vespa.config.server.session.LocalSessionRepo; @@ -69,8 +73,11 @@ import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -80,6 +87,7 @@ import java.util.stream.Collectors; import static com.yahoo.config.model.api.container.ContainerServiceType.CLUSTERCONTROLLER_CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER; import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER; +import static com.yahoo.config.model.api.container.ContainerServiceType.METRICS_PROXY_CONTAINER; import static com.yahoo.vespa.config.server.tenant.TenantRepository.HOSTED_VESPA_TENANT; import static java.nio.file.Files.readAttributes; @@ -460,7 +468,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); if (tenant == null) throw new NotFoundException("Tenant '" + applicationId.tenant() + "' not found"); long sessionId = getSessionIdForApplication(tenant, applicationId); - RemoteSession session = tenant.getRemoteSessionRepo().getSession(sessionId, 0); + RemoteSession session = tenant.getRemoteSessionRepo().getSession(sessionId); return session.ensureApplicationLoaded().getForVersionOrLatest(version, clock.instant()); } catch (NotFoundException e) { log.log(LogLevel.WARNING, "Failed getting application for '" + applicationId + "': " + e.getMessage()); @@ -633,6 +641,21 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return tenantRepository.getTenant(tenantName).getApplicationRepo().activeApplications(); } + // ---------------- Metrics ------------------------------------------------------------------------ + + public MetricsResponse getMetrics(ApplicationId applicationId) { + var metricsRetriever = new MetricsRetriever(); + var clusters = getClustersOfApplication(applicationId); + var clusterMetrics = new LinkedHashMap<ClusterInfo, MetricsAggregator>(); + + clusters.forEach(cluster -> { + var metrics = metricsRetriever.requestMetricsForCluster(cluster); + clusterMetrics.put(cluster, metrics); + }); + + return new MetricsResponse(200, applicationId, clusterMetrics); + } + // ---------------- Misc operations ---------------------------------------------------------------- public ApplicationMetaData getMetadataFromSession(Tenant tenant, long sessionId) { @@ -750,16 +773,47 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye .anyMatch(serviceInfo -> serviceInfo.getServiceType().equalsIgnoreCase("logserver"))) .findFirst().orElseThrow(() -> new IllegalArgumentException("Could not find HostInfo for LogServer")); - ServiceInfo containerServiceInfo = logServerHostInfo.getServices().stream() - .filter(service -> List.of(LOGSERVER_CONTAINER.serviceName, CONTAINER.serviceName).contains(service.getServiceType())) + ServiceInfo serviceInfo = logServerHostInfo.getServices().stream().filter(service -> List.of(LOGSERVER_CONTAINER.serviceName, CONTAINER.serviceName).contains(service.getServiceType())) .findFirst().orElseThrow(() -> new IllegalArgumentException("No container running on logserver host")); + int port = servicePort(serviceInfo); + return "http://" + logServerHostInfo.getHostname() + ":" + port + "/logs"; + } - int port = containerServiceInfo.getPorts().stream() + private int servicePort(ServiceInfo serviceInfo) { + int port = serviceInfo.getPorts().stream() .filter(portInfo -> portInfo.getTags().stream().anyMatch(tag -> tag.equalsIgnoreCase("http"))) .findFirst().orElseThrow(() -> new IllegalArgumentException("Could not find HTTP port")) .getPort(); + return port; + } - return "http://" + logServerHostInfo.getHostname() + ":" + port + "/logs"; + /** Finds the hosts of an application, grouped by cluster name */ + private Collection<ClusterInfo> getClustersOfApplication(ApplicationId applicationId) { + Application application = getApplication(applicationId); + Map<String, ClusterInfo> clusters = new HashMap<>(); + application.getModel().getHosts().stream() + .filter(host -> host.getServices().stream().noneMatch(serviceInfo -> serviceInfo.getServiceType().equalsIgnoreCase("logserver"))) + .forEach(hostInfo -> { + ServiceInfo metricsService = getServiceInfoByType(hostInfo, METRICS_PROXY_CONTAINER.serviceName); + ServiceInfo clusterServiceInfo = getServiceInfoByType(hostInfo, "container", "searchnode"); + ClusterInfo clusterInfo = createClusterInfo(clusterServiceInfo); + URI host = URI.create("http://" + hostInfo.getHostname() + ":" + servicePort(metricsService) + "/metrics/v1/values?consumer=Vespa"); + clusters.computeIfAbsent(clusterInfo.getClusterId(), c -> clusterInfo).addHost(host); + } + ); + return clusters.values(); + + } + + private ServiceInfo getServiceInfoByType(HostInfo hostInfo, String... types) { + List<String> type = List.of(types); + return hostInfo.getServices().stream().filter(serviceInfo -> type.contains(serviceInfo.getServiceType())).findFirst().orElseThrow(); + } + + private ClusterInfo createClusterInfo(ServiceInfo serviceInfo) { + String clusterName = serviceInfo.getServiceName(); + ClusterInfo.ClusterType clusterType = serviceInfo.getServiceType().equals("searchnode") ? ClusterInfo.ClusterType.content : ClusterInfo.ClusterType.container; + return new ClusterInfo(clusterName, clusterType); } /** Returns version to use when deploying application in given environment */ diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java index d55e07540d6..d0f8005ace1 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ConfigConvergenceChecker.java @@ -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.application; +import ai.vespa.util.http.VespaClientBuilderFactory; import com.fasterxml.jackson.databind.JsonNode; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; @@ -17,8 +18,9 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; import java.net.URI; import java.time.Duration; import java.util.ArrayList; @@ -55,6 +57,7 @@ public class ConfigConvergenceChecker extends AbstractComponent { ); private final StateApiFactory stateApiFactory; + private final VespaClientBuilderFactory clientBuilderFactory = new VespaClientBuilderFactory(); @Inject public ConfigConvergenceChecker() { @@ -97,6 +100,11 @@ public class ConfigConvergenceChecker extends AbstractComponent { } } + @Override + public void deconstruct() { + clientBuilderFactory.close(); + } + @Path(statePath) public interface StateApi { @Path(configSubPath) @@ -152,8 +160,11 @@ public class ConfigConvergenceChecker extends AbstractComponent { return false; } - private static Client createClient(Duration timeout) { - return ClientBuilder.newBuilder() + private Client createClient(Duration timeout) { + return clientBuilderFactory.newBuilder() + .register( + (ClientRequestFilter) ctx -> + ctx.getHeaders().put(HttpHeaders.USER_AGENT, List.of("config-convergence-checker"))) .property(ClientProperties.CONNECT_TIMEOUT, (int) timeout.toMillis()) .property(ClientProperties.READ_TIMEOUT, (int) timeout.toMillis()) .build(); 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 d875385d14d..64148ba5de4 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 @@ -136,6 +136,7 @@ public class ModelContextImpl implements ModelContext { private final boolean useAdaptiveDispatch; private final boolean dispatchWithProtobuf; private final Optional<TlsSecrets> tlsSecrets; + private final boolean enableGroupingSessionCache; public Properties(ApplicationId applicationId, boolean multitenantFromConfig, @@ -172,6 +173,8 @@ public class ModelContextImpl implements ModelContext { this.useAdaptiveDispatch = Flags.USE_ADAPTIVE_DISPATCH.bindTo(flagSource) .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); this.tlsSecrets = tlsSecrets; + this.enableGroupingSessionCache = Flags.ENABLE_GROUPING_SESSION_CACHE.bindTo(flagSource) + .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); } @Override @@ -228,6 +231,11 @@ public class ModelContextImpl implements ModelContext { @Override public Optional<TlsSecrets> tlsSecrets() { return tlsSecrets; } + + @Override + public boolean enableGroupingSessionCache() { + return enableGroupingSessionCache; + } } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java index 110e73bcdf9..484124991d9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClient.java @@ -288,22 +288,14 @@ public class ZooKeeperClient { for (Map.Entry<ConfigDefinitionKey, UnparsedConfigDefinition> entry : configDefs.entrySet()) { ConfigDefinitionKey key = entry.getKey(); String contents = entry.getValue().getUnparsedContent(); - write(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); - write(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); + writeConfigDefinition(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); + writeConfigDefinition(key.getName(), key.getNamespace(), getZooKeeperAppPath(ConfigCurator.DEFCONFIGS_ZK_SUBPATH).getAbsolute(), contents); } logger.log(LogLevel.FINE, configDefs.size() + " user config definitions"); } - private void write(String name, String namespace, String path, String data) { - write(name, namespace, "", path, com.yahoo.text.Utf8.toBytes(data)); - } - - private void write(String name, String namespace, String version, String path, byte[] data) { - configCurator.putDefData( - ("".equals(namespace)) ? name : (namespace + "." + name), - version, - path, - data); + private void writeConfigDefinition(String name, String namespace, String path, String data) { + configCurator.putDefData(namespace + "." + name, path, com.yahoo.text.Utf8.toBytes(data)); } private void write(Version vespaVersion, FileRegistry fileRegistry) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java index 3d2ecd4a2ca..6559292645c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java @@ -49,7 +49,8 @@ public class HttpErrorResponse extends HttpResponse { REQUEST_TIMEOUT, UNKNOWN_VESPA_VERSION, PARENT_HOST_NOT_READY, - CERTIFICATE_NOT_READY + CERTIFICATE_NOT_READY, + LOAD_BALANCER_NOT_READY } public static HttpErrorResponse notFoundError(String msg) { @@ -100,6 +101,10 @@ public class HttpErrorResponse extends HttpResponse { return new HttpErrorResponse(CONFLICT, errorCodes.CERTIFICATE_NOT_READY.name(), msg); } + public static HttpErrorResponse loadBalancerNotReady(String msg) { + return new HttpErrorResponse(CONFLICT, errorCodes.LOAD_BALANCER_NOT_READY.name(), msg); + } + @Override public void render(OutputStream stream) throws IOException { new JsonFormat(true).encode(stream, slime); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java index 20ee77be9fe..7cdb8d3a60c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.http; import com.yahoo.config.provision.ApplicationLockException; import com.yahoo.config.provision.CertificateNotReadyException; import com.yahoo.config.provision.ParentHostUnavailableException; +import com.yahoo.config.provision.exception.LoadBalancerServiceException; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; @@ -67,6 +68,8 @@ public class HttpHandler extends LoggingRequestHandler { return HttpErrorResponse.parentHostNotReady(getMessage(e, request)); } catch (CertificateNotReadyException e) { return HttpErrorResponse.certificateNotReady(getMessage(e, request)); + } catch (LoadBalancerServiceException e) { + return HttpErrorResponse.loadBalancerNotReady(getMessage(e, request)); } catch (Exception e) { log.log(LogLevel.WARNING, "Unexpected exception handling a config server request", e); return HttpErrorResponse.internalServerError(getMessage(e, request)); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java deleted file mode 100644 index 3594c801ca8..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; - -import com.yahoo.jdisc.Response; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; -import com.yahoo.text.Utf8; -import com.yahoo.vespa.config.SlimeUtils; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; -import com.yahoo.vespa.config.server.http.StaticResponse; - -import java.util.Arrays; -import java.util.List; - -import static com.yahoo.yolean.Exceptions.uncheck; - -/** - * @author hakonhall - */ -public class V1Response extends StaticResponse { - public V1Response(String flagsV1Uri, String... names) { - super(Response.Status.OK, HttpConfigResponse.JSON_CONTENT_TYPE, generateBody(flagsV1Uri, Arrays.asList(names))); - } - - private static String generateBody(String flagsV1Uri, List<String> names) { - Slime slime = new Slime(); - Cursor root = slime.setObject(); - names.forEach(name -> { - Cursor data = root.setObject(name); - data.setString("url", flagsV1Uri + "/" + name); - }); - return Utf8.toString(uncheck(() -> SlimeUtils.toJsonBytes(slime))); - } -} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java index 865805d1258..e18c6ad6c56 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java @@ -104,6 +104,10 @@ public class ApplicationHandler extends HttpHandler { return applicationRepository.getLogs(applicationId, hostname, apiParams); } + if (isMetricsRequest(request)) { + return applicationRepository.getMetrics(applicationId); + } + if (isIsSuspendedRequest(request)) { return new ApplicationSuspendedResponse(applicationRepository.isSuspended(applicationId)); } @@ -144,6 +148,7 @@ public class ApplicationHandler extends HttpHandler { "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*", + "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/metrics", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*", "http://*/application/v2/tenant/*/application/*/logs", "http://*/application/v2/tenant/*/application/*"); @@ -154,6 +159,11 @@ public class ApplicationHandler extends HttpHandler { request.getUri().getPath().endsWith("/suspended"); } + private static boolean isMetricsRequest(HttpRequest request) { + return getBindingMatch(request).groupCount() == 7 && + request.getUri().getPath().endsWith("/metrics"); + } + private static boolean isLogRequest(HttpRequest request) { return getBindingMatch(request).groupCount() == 4 && request.getUri().getPath().endsWith("/logs"); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/MetricsResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/MetricsResponse.java new file mode 100644 index 00000000000..0abc59f54b2 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/MetricsResponse.java @@ -0,0 +1,56 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.http.v2; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.config.server.http.HttpConfigResponse; +import com.yahoo.vespa.config.server.metrics.ClusterInfo; +import com.yahoo.vespa.config.server.metrics.MetricsAggregator; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; + +/** + * @author olaa + */ +public class MetricsResponse extends HttpResponse { + + private final Slime slime = new Slime(); + + public MetricsResponse(int status, ApplicationId applicationId, Map<ClusterInfo, MetricsAggregator> aggregatedMetrics) { + super(status); + + Cursor application = slime.setObject(); + application.setString("applicationId", applicationId.serializedForm()); + + Cursor clusters = application.setArray("clusters"); + + for (var entry : aggregatedMetrics.entrySet()) { + Cursor cluster = clusters.addObject(); + cluster.setString("clusterId", entry.getKey().getClusterId()); + cluster.setString("clusterType", entry.getKey().getClusterType().name()); + + MetricsAggregator aggregator = entry.getValue(); + Cursor metrics = cluster.setObject("metrics"); + aggregator.aggregateQueryRate().ifPresent(queryRate -> metrics.setDouble("queriesPerSecond", queryRate)); + aggregator.aggregateFeedRate().ifPresent(feedRate -> metrics.setDouble("feedPerSecond", feedRate)); + aggregator.aggregateDocumentCount().ifPresent(documentCount -> metrics.setDouble("documentCount", documentCount)); + aggregator.aggregateQueryLatency().ifPresent(queryLatency -> metrics.setDouble("queryLatency",queryLatency)); + aggregator.aggregateFeedLatency().ifPresent(feedLatency -> metrics.setDouble("feedLatency", feedLatency)); + } + } + + @Override + public void render(OutputStream outputStream) throws IOException { + new JsonFormat(false).encode(outputStream, slime); + } + + @Override + public String getContentType() { + return HttpConfigResponse.JSON_CONTENT_TYPE; + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterInfo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterInfo.java new file mode 100644 index 00000000000..7507b5c4c2c --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterInfo.java @@ -0,0 +1,56 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.metrics; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +/** + * @author olaa + */ +public class ClusterInfo { + + private final String clusterId; + private final ClusterType clusterType; + private final List<URI> hostnames; + + public ClusterInfo(String clusterId, ClusterType clusterType) { + this(clusterId, clusterType, new ArrayList<>()); + } + + public ClusterInfo(String clusterId, ClusterType clusterType, List<URI> hostnames) { + this.clusterId = clusterId; + this.clusterType = clusterType; + this.hostnames = hostnames; + } + + public String getClusterId() { + return clusterId; + } + + public ClusterType getClusterType() { + return clusterType; + } + + public List<URI> getHostnames() { + return hostnames; + } + + public void addHost(URI host) { + hostnames.add(host); + } + + public enum ClusterType { + content, + container; + + public static boolean isValidType(String enumString) { + try { + valueOf(enumString); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + }; +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/MetricsAggregator.java b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/MetricsAggregator.java new file mode 100644 index 00000000000..8fa08275ad5 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/MetricsAggregator.java @@ -0,0 +1,86 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.metrics; + +import java.time.Instant; +import java.util.Optional; + +/** + * @author olaa + * @author ogronnesby + */ +public class MetricsAggregator { + + private LatencyMetrics feed; + private LatencyMetrics qr; + private LatencyMetrics container; + private Double documentCount; + private Instant timestamp; + + public MetricsAggregator addFeedLatency(double sum, double count) { + this.feed = combineLatency(this.feed, sum, count); + return this; + } + + public MetricsAggregator addQrLatency(double sum, double count) { + this.qr = combineLatency(this.qr, sum, count); + return this; + } + + public MetricsAggregator addContainerLatency(double sum, double count) { + this.container = combineLatency(this.container, sum, count); + return this; + } + + public MetricsAggregator addDocumentCount(double count) { + this.documentCount = (this.documentCount == null ? 0.0 : this.documentCount) + count; + return this; + } + + public MetricsAggregator setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + return this; + } + + public Optional<Double> aggregateFeedLatency() { + return Optional.ofNullable(feed).map(m -> m.latencySum / m.latencyCount).filter(num -> !num.isNaN()); + + } + + public Optional<Double> aggregateFeedRate() { + return Optional.ofNullable(feed).map(m -> m.latencyCount / 60); + } + + public Optional<Double> aggregateQueryLatency() { + if (container == null && qr == null) return Optional.empty(); + var c = Optional.ofNullable(container).orElseGet(LatencyMetrics::new); + var q = Optional.ofNullable(qr).orElseGet(LatencyMetrics::new); + return Optional.of((c.latencySum + q.latencySum) / (c.latencyCount + q.latencyCount)).filter(num -> !num.isNaN()); + } + + public Optional<Double> aggregateQueryRate() { + if (container == null && qr == null) return Optional.empty(); + var c = Optional.ofNullable(container).orElseGet(LatencyMetrics::new); + var q = Optional.ofNullable(qr).orElseGet(LatencyMetrics::new); + return Optional.of((c.latencyCount + q.latencyCount) / 60); + } + + public Optional<Double> aggregateDocumentCount() { + return Optional.ofNullable(documentCount); + } + + public Instant getTimestamp() { + return timestamp; + } + + private LatencyMetrics combineLatency(LatencyMetrics metricsOrNull, double sum, double count) { + var metrics = Optional.ofNullable(metricsOrNull).orElseGet(LatencyMetrics::new); + metrics.latencyCount += count; + metrics.latencySum += sum; + return metrics; + } + + private static class LatencyMetrics { + double latencySum; + double latencyCount; + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/MetricsRetriever.java b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/MetricsRetriever.java new file mode 100644 index 00000000000..4fbeae11758 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/MetricsRetriever.java @@ -0,0 +1,97 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.config.server.metrics; + +import com.yahoo.slime.ArrayTraverser; +import com.yahoo.slime.Inspector; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.config.SlimeUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.time.Instant; +import java.util.logging.Logger; + + +/** + * Client for reaching out to nodes in an application instance and get their + * metrics. + * + * @author olaa + * @author ogronnesby + */ +public class MetricsRetriever { + private static final Logger log = Logger.getLogger(MetricsRetriever.class.getName()); + private final HttpClient httpClient = HttpClientBuilder.create().build(); + + /** + * Call the metrics API on each host in the cluster and aggregate the metrics + * into a single value. + */ + public MetricsAggregator requestMetricsForCluster(ClusterInfo clusterInfo) { + var aggregator = new MetricsAggregator(); + clusterInfo.getHostnames().forEach(host -> getHostMetrics(host, aggregator)); + return aggregator; + } + + private void getHostMetrics(URI hostURI, MetricsAggregator metrics) { + Slime responseBody = doMetricsRequest(hostURI); + var parseError = responseBody.get().field("error_message"); + + if (parseError.valid()) { + log.info("Failed to retrieve metrics from " + hostURI + ": " + parseError.asString()); + } + + Inspector services = responseBody.get().field("services"); + services.traverse((ArrayTraverser) (i, servicesInspector) -> { + parseService(servicesInspector, metrics); + }); + } + + private Slime doMetricsRequest(URI hostURI) { + HttpGet get = new HttpGet(hostURI); + try { + HttpResponse response = httpClient.execute(get); + InputStream is = response.getEntity().getContent(); + Slime slime = SlimeUtils.jsonToSlime(is.readAllBytes()); + is.close(); + return slime; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void parseService(Inspector service, MetricsAggregator metrics) { + String serviceName = service.field("name").asString(); + Instant timestamp = Instant.ofEpochSecond(service.field("timestamp").asLong()); + metrics.setTimestamp(timestamp); + service.field("metrics").traverse((ArrayTraverser) (i, m) -> { + Inspector values = m.field("values"); + switch (serviceName) { + case "vespa.container": + metrics.addContainerLatency( + values.field("query_latency.sum").asDouble(), + values.field("query_latency.count").asDouble()); + metrics.addFeedLatency( + values.field("feed_latency.sum").asDouble(), + values.field("feed_latency.count").asDouble()); + break; + case "vespa.qrserver": + metrics.addQrLatency( + values.field("query_latency.sum").asDouble(), + values.field("query_latency.count").asDouble()); + break; + case "vespa.distributor": + metrics.addDocumentCount(values.field("vds.distributor.docsstored.average").asDouble()); + break; + } + }); + + } + +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java b/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java index 408bf44e733..b2813be5456 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdater.java @@ -97,7 +97,7 @@ public class ZKMetricUpdater extends TimerTask { buffer.clear(); } while (nread >= 0); - return Optional.of(baos.toString("UTF-8")); + return Optional.of(baos.toString(StandardCharsets.UTF_8)); } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { log.warning("Failure in retrieving monitoring data: (" + e.getClass().getName() + ") " + e.getMessage()); return Optional.empty(); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java index 7fd29368ab3..2c6d7de8b0c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java @@ -36,7 +36,7 @@ import java.util.Optional; // TODO: Separate the "application store" and "session" aspects - the latter belongs in the HTTP layer -bratseth public class LocalSession extends Session implements Comparable<LocalSession> { - private final ApplicationPackage applicationPackage; + protected final ApplicationPackage applicationPackage; private final TenantApplications applicationRepo; private final SessionPreparer sessionPreparer; private final SessionContext sessionContext; @@ -47,7 +47,6 @@ public class LocalSession extends Session implements Comparable<LocalSession> { * * @param sessionId The session id for this session. */ - // TODO tenant in SessionContext? public LocalSession(TenantName tenant, long sessionId, SessionPreparer sessionPreparer, SessionContext sessionContext) { super(tenant, sessionId, sessionContext.getSessionZooKeeperClient()); this.serverDB = sessionContext.getServerDBSessionDir(); @@ -211,7 +210,7 @@ public class LocalSession extends Session implements Comparable<LocalSession> { private final String pathToDelete; - public DeleteOperation(String pathToDelete) { + DeleteOperation(String pathToDelete) { this.pathToDelete = pathToDelete; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java index f6d73f33504..ad7a5116ac4 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionRepo.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.concurrent.InThreadExecutorService; -import com.yahoo.concurrent.StripedExecutor; import com.yahoo.config.provision.TenantName; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; @@ -16,7 +14,6 @@ import com.yahoo.vespa.curator.Curator; import java.io.File; import java.io.FilenameFilter; import java.time.Clock; -import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,23 +37,20 @@ public class LocalSessionRepo extends SessionRepo<LocalSession> { private final Clock clock; private final Curator curator; private final Executor zkWatcherExecutor; + private final TenantFileSystemDirs tenantFileSystemDirs; - public LocalSessionRepo(TenantName tenantName, GlobalComponentRegistry registry, TenantFileSystemDirs tenantFileSystemDirs, LocalSessionLoader loader) { - this(registry.getClock(), registry.getCurator(), registry.getConfigserverConfig().sessionLifetime(), - command -> registry.getZkWatcherExecutor().execute(tenantName, command)); - loadSessions(tenantFileSystemDirs.sessionsPath(), loader); + public LocalSessionRepo(TenantName tenantName, GlobalComponentRegistry componentRegistry, LocalSessionLoader loader) { + this(tenantName, componentRegistry); + loadSessions(loader); } // Constructor public only for testing - public LocalSessionRepo(Clock clock, Curator curator) { - this(clock, curator, Duration.ofDays(1).toMillis(), Runnable::run); - } - - private LocalSessionRepo(Clock clock, Curator curator, long sessionLifetime, Executor zkWatcherExecutor) { - this.clock = clock; - this.curator = curator; - this.sessionLifetime = sessionLifetime; - this.zkWatcherExecutor = zkWatcherExecutor; + public LocalSessionRepo(TenantName tenantName, GlobalComponentRegistry componentRegistry) { + this.clock = componentRegistry.getClock(); + this.curator = componentRegistry.getCurator(); + this.sessionLifetime = componentRegistry.getConfigserverConfig().sessionLifetime(); + this.zkWatcherExecutor = command -> componentRegistry.getZkWatcherExecutor().execute(tenantName, command); + this.tenantFileSystemDirs = new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenantName); } @Override @@ -68,17 +62,17 @@ public class LocalSessionRepo extends SessionRepo<LocalSession> { sessionStateWatchers.put(sessionId, new LocalSessionStateWatcher(fileCache, session, this, zkWatcherExecutor)); } - private void loadSessions(File applicationsDir, LocalSessionLoader loader) { - File[] applications = applicationsDir.listFiles(sessionApplicationsFilter); - if (applications == null) { + private void loadSessions(LocalSessionLoader loader) { + File[] sessions = tenantFileSystemDirs.sessionsPath().listFiles(sessionApplicationsFilter); + if (sessions == null) { return; } - for (File application : applications) { + for (File session : sessions) { try { - addSession(loader.loadSession(Long.parseLong(application.getName()))); + addSession(loader.loadSession(Long.parseLong(session.getName()))); } catch (IllegalArgumentException e) { - log.log(LogLevel.WARNING, "Could not load application '" + - application.getAbsolutePath() + "':" + e.getMessage() + ", skipping it."); + log.log(LogLevel.WARNING, "Could not load session '" + + session.getAbsolutePath() + "':" + e.getMessage() + ", skipping it."); } } } @@ -118,7 +112,12 @@ public class LocalSessionRepo extends SessionRepo<LocalSession> { transaction.commit(); } - public void deleteAllSessions() { + public void close() { + deleteAllSessions(); + tenantFileSystemDirs.delete(); + } + + private void deleteAllSessions() { List<LocalSession> sessions = new ArrayList<>(listSessions()); for (LocalSession session : sessions) { deleteSession(session); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java index 53a472c2b67..d5a87b3c45e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSessionStateWatcher.java @@ -63,9 +63,9 @@ public class LocalSessionStateWatcher { public void nodeChanged() { zkWatcherExecutor.execute(() -> { try { - ChildData data = fileCache.getCurrentData(); - if (data != null) { - sessionChanged(Session.Status.parse(Utf8.toString(fileCache.getCurrentData().getData()))); + ChildData node = fileCache.getCurrentData(); + if (node != null) { + sessionChanged(Session.Status.parse(Utf8.toString(node.getData()))); } } catch (Exception e) { log.log(LogLevel.WARNING, session.logPre() + "Error handling session changed for session " + getSessionId(), e); 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 e0727effeda..c37f4aa8401 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 @@ -3,9 +3,7 @@ package com.yahoo.vespa.config.server.session; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; -import com.yahoo.concurrent.InThreadExecutorService; import com.yahoo.concurrent.StripedExecutor; -import com.yahoo.concurrent.ThreadFactoryFactory; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.log.LogLevel; @@ -20,7 +18,6 @@ import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.flags.Flags; -import com.yahoo.vespa.flags.InMemoryFlagSource; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; @@ -33,8 +30,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -68,7 +63,6 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { ReloadHandler reloadHandler, TenantName tenantName, TenantApplications applicationRepo) { - this.curator = registry.getCurator(); this.sessionsPath = TenantRepository.getSessionsPath(tenantName); this.applicationRepo = applicationRepo; @@ -85,20 +79,6 @@ public class RemoteSessionRepo extends SessionRepo<RemoteSession> { this.directoryCache.start(); } - // For testing only - public RemoteSessionRepo(TenantName tenantName) { - this.curator = null; - this.remoteSessionFactory = null; - this.reloadHandler = null; - this.tenantName = tenantName; - this.sessionsPath = TenantRepository.getSessionsPath(tenantName); - this.metrics = null; - this.directoryCache = null; - this.applicationRepo = null; - this.flagSource = new InMemoryFlagSource(); - this.zkWatcherExecutor = Runnable::run; - } - public List<Long> getSessions() { return getSessionList(curator.getChildren(sessionsPath)); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java index 653a2616cbe..a3fad3d7322 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionStateWatcher.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.concurrent.StripedExecutor; -import com.yahoo.config.provision.TenantName; import com.yahoo.log.LogLevel; import com.yahoo.text.Utf8; import com.yahoo.vespa.config.server.ReloadHandler; @@ -72,12 +70,12 @@ public class RemoteSessionStateWatcher { } } - public void nodeChanged() { + private void nodeChanged() { zkWatcherExecutor.execute(() -> { try { - ChildData data = fileCache.getCurrentData(); - if (data != null) { - sessionChanged(Session.Status.parse(Utf8.toString(fileCache.getCurrentData().getData()))); + ChildData node = fileCache.getCurrentData(); + if (node != null) { + sessionChanged(Session.Status.parse(Utf8.toString(node.getData()))); } } catch (Exception e) { log.log(LogLevel.WARNING, session.logPre() + "Error handling session changed for session " + getSessionId(), e); 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 cc46a157b34..fad5685d6fa 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 @@ -54,7 +54,6 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { public SessionFactoryImpl(GlobalComponentRegistry globalComponentRegistry, TenantApplications applicationRepo, - TenantFileSystemDirs tenantFileSystemDirs, HostValidator<ApplicationId> hostRegistry, TenantName tenant) { this.hostRegistry = hostRegistry; @@ -65,7 +64,7 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { this.sessionCounter = new SessionCounter(globalComponentRegistry.getConfigCurator(), tenant); this.sessionsPath = TenantRepository.getSessionsPath(tenant); this.applicationRepo = applicationRepo; - this.tenantFileSystemDirs = tenantFileSystemDirs; + this.tenantFileSystemDirs = new TenantFileSystemDirs(globalComponentRegistry.getConfigServerDB(), tenant); this.serverId = globalComponentRegistry.getConfigserverConfig().serverId(); this.nodeFlavors = globalComponentRegistry.getZone().nodeFlavors(); this.clock = globalComponentRegistry.getClock(); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepo.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepo.java index 415ff268309..3400504fb58 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepo.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepo.java @@ -1,14 +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.transaction.AbstractTransaction; -import com.yahoo.transaction.NestedTransaction; -import com.yahoo.transaction.Transaction; -import com.yahoo.vespa.config.server.TimeoutBudget; -import com.yahoo.vespa.config.server.NotFoundException; - -import java.time.Clock; -import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -29,16 +21,10 @@ public class SessionRepo<SESSIONTYPE extends Session> { sessions.put(session.getSessionId(), session); } - public synchronized SESSIONTYPE removeSession(long id) { + synchronized void removeSession(long id) { if ( ! sessions.containsKey(id)) throw new IllegalArgumentException("No session with id '" + id + "' exists"); - return sessions.remove(id); - } - - public void removeSession(long id, NestedTransaction nestedTransaction) { - SessionRepoTransaction transaction = new SessionRepoTransaction(); - transaction.addRemoveOperation(id); - nestedTransaction.add(transaction); + sessions.remove(id); } /** @@ -51,90 +37,8 @@ public class SessionRepo<SESSIONTYPE extends Session> { return sessions.get(id); } - /** - * Gets a Session with a timeout - * - * @param id session id - * @param timeoutInMillis timeout for getting session (loops and wait for session to show up if not found) - * @return a session belonging to the id supplied, or null if no session with the id was found - */ - public synchronized SESSIONTYPE getSession(long id, long timeoutInMillis) { - try { - return internalGetSession(id, timeoutInMillis); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while retrieving session with id " + id); - } - } - - private synchronized SESSIONTYPE internalGetSession(long id, long timeoutInMillis) throws InterruptedException { - TimeoutBudget timeoutBudget = new TimeoutBudget(Clock.systemUTC(), Duration.ofMillis(timeoutInMillis)); - do { - SESSIONTYPE session = getSession(id); - if (session != null) { - return session; - } - wait(100); - } while (timeoutBudget.hasTimeLeft()); - throw new NotFoundException("Unable to retrieve session with id " + id + " before timeout was reached"); - } - public synchronized Collection<SESSIONTYPE> listSessions() { return new ArrayList<>(sessions.values()); } - public class SessionRepoTransaction extends AbstractTransaction { - - void addRemoveOperation(long sessionIdToRemove) { - add(new RemoveOperation(sessionIdToRemove)); - } - - @Override - public void prepare() { } - - @Override - @SuppressWarnings("unchecked") - public void commit() { - for (Operation operation : operations()) - ((SessionOperation)operation).commit(); - } - - @Override - @SuppressWarnings("unchecked") - public void rollbackOrLog() { - for (Operation operation : operations()) - ((SessionOperation)operation).rollback(); - } - - abstract class SessionOperation implements Transaction.Operation { - - abstract void commit(); - - abstract void rollback(); - - } - - public class RemoveOperation extends SessionOperation { - - private final long sessionIdToRemove; - private SESSIONTYPE removed = null; - - RemoveOperation(long sessionIdToRemove) { - this.sessionIdToRemove = sessionIdToRemove; - } - - @Override - public void commit() { - removed = removeSession(sessionIdToRemove); - } - - @Override - public void rollback() { - if (removed != null) - addSession(removed); - } - - } - - } - } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java index 0aed3977625..bbd3ae55f10 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java @@ -6,7 +6,6 @@ import com.yahoo.path.Path; import com.yahoo.vespa.config.server.ReloadHandler; import com.yahoo.vespa.config.server.RequestHandler; import com.yahoo.vespa.config.server.application.TenantApplications; -import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.session.RemoteSessionRepo; import com.yahoo.vespa.config.server.session.SessionFactory; @@ -22,13 +21,11 @@ import java.util.Optional; * * @author vegardh * @author Ulf Lilleengen - * @since 5.1.26 */ public class Tenant implements TenantHandlerProvider { static final String SESSIONS = "sessions"; static final String APPLICATIONS = "applications"; - static final String LOCKS = "locks"; private final TenantName name; private final RemoteSessionRepo remoteSessionRepo; @@ -38,7 +35,6 @@ public class Tenant implements TenantHandlerProvider { private final TenantApplications applicationRepo; private final RequestHandler requestHandler; private final ReloadHandler reloadHandler; - private final TenantFileSystemDirs tenantFileSystemDirs; private final Curator curator; Tenant(TenantName name, @@ -49,8 +45,7 @@ public class Tenant implements TenantHandlerProvider { RequestHandler requestHandler, ReloadHandler reloadHandler, TenantApplications applicationRepo, - Curator curator, - TenantFileSystemDirs tenantFileSystemDirs) { + Curator curator) { this.name = name; this.path = path; this.requestHandler = requestHandler; @@ -59,7 +54,6 @@ public class Tenant implements TenantHandlerProvider { this.sessionFactory = sessionFactory; this.localSessionRepo = localSessionRepo; this.applicationRepo = applicationRepo; - this.tenantFileSystemDirs = tenantFileSystemDirs; this.curator = curator; } @@ -147,10 +141,9 @@ public class Tenant implements TenantHandlerProvider { * Called by watchers as a reaction to {@link #delete()}. */ void close() { - tenantFileSystemDirs.delete(); // Deletes all local files. remoteSessionRepo.close(); // Closes watchers and clears memory. applicationRepo.close(); // Closes watchers. - localSessionRepo.deleteAllSessions(); // Closes watchers, clears memory, and deletes some local files and ZK session state. + localSessionRepo.close(); // Closes watchers, clears memory, and deletes local files and ZK session state. } /** Deletes the tenant tree from ZooKeeper (application and session status for the tenant) and triggers {@link #close()}. */ 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 861b2f91c8f..23a6abb2c6c 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 @@ -33,7 +33,6 @@ public class TenantBuilder { private TenantRequestHandler reloadHandler; private RequestHandler requestHandler; private RemoteSessionFactory remoteSessionFactory; - private TenantFileSystemDirs tenantFileSystemDirs; private HostValidator<ApplicationId> hostValidator; private TenantBuilder(GlobalComponentRegistry componentRegistry, TenantName tenant) { @@ -81,7 +80,6 @@ public class TenantBuilder { createApplicationRepo(); createRemoteSessionFactory(); createRemoteSessionRepo(); - createServerDbDirs(); createSessionFactory(); createLocalSessionRepo(); return new Tenant(tenant, @@ -92,20 +90,18 @@ public class TenantBuilder { requestHandler, reloadHandler, applicationRepo, - componentRegistry.getCurator(), - tenantFileSystemDirs); + componentRegistry.getCurator()); } private void createLocalSessionRepo() { if (localSessionRepo == null) { - localSessionRepo = new LocalSessionRepo(tenant, componentRegistry, tenantFileSystemDirs, localSessionLoader); + localSessionRepo = new LocalSessionRepo(tenant, componentRegistry, localSessionLoader); } } private void createSessionFactory() { if (sessionFactory == null || localSessionLoader == null) { - SessionFactoryImpl impl = new SessionFactoryImpl(componentRegistry, applicationRepo, - tenantFileSystemDirs, hostValidator, tenant); + SessionFactoryImpl impl = new SessionFactoryImpl(componentRegistry, applicationRepo, hostValidator, tenant); if (sessionFactory == null) { sessionFactory = impl; } @@ -155,11 +151,5 @@ public class TenantBuilder { } } - private void createServerDbDirs() { - if (tenantFileSystemDirs == null) { - tenantFileSystemDirs = new TenantFileSystemDirs(componentRegistry.getConfigServerDB(), tenant); - } - } - public TenantName getTenantName() { return tenant; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java index ad2472add89..2e09d830783 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRepository.java @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.concurrent.StripedExecutor; -import com.yahoo.concurrent.ThreadFactoryFactory; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.TenantName; import com.yahoo.log.LogLevel; @@ -285,7 +284,7 @@ public class TenantRepository { tenants.get(name).delete(); } - public synchronized void closeTenant(TenantName name) { + private synchronized void closeTenant(TenantName name) { Tenant tenant = tenants.remove(name); if (tenant == null) throw new IllegalArgumentException("Closing '" + name + "' failed, tenant does not exist"); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java index 92d7589ea43..56709225c1d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/zookeeper/ConfigCurator.java @@ -284,23 +284,11 @@ public class ConfigCurator { * Puts config definition data and metadata into ZK. * * @param name The config definition name (including namespace) - * @param version The config definition version * @param path /zoopath * @param data The contents to write to ZK (as a byte array) */ - public void putDefData(String name, String version, String path, byte[] data) { - if (version == null) { + public void putDefData(String name, String path, byte[] data) { putData(path, name, data); - } else { - String fullPath = createFullPath(path, name + "," + version); - if (exists(fullPath)) { - // TODO This should not happen when all the compatibility hacks in 5.1 have been removed - log.log(LogLevel.INFO, "There already exists a config definition '" + name + "', skipping feeding this one to ZooKeeper"); - } - else { - putData(fullPath, data); - } - } } /** diff --git a/configserver/src/main/java/com/yahoo/vespa/serviceview/ConfigServerLocation.java b/configserver/src/main/java/com/yahoo/vespa/serviceview/ConfigServerLocation.java index 5dcdfcdaf37..cc452421d2d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/serviceview/ConfigServerLocation.java +++ b/configserver/src/main/java/com/yahoo/vespa/serviceview/ConfigServerLocation.java @@ -1,20 +1,28 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.serviceview; +import ai.vespa.util.http.VespaClientBuilderFactory; +import com.google.inject.Inject; import com.yahoo.cloud.config.ConfigserverConfig; +import com.yahoo.component.AbstractComponent; /** * Wrapper for settings from the cloud.config.configserver config. * - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + * @author Steinar Knutsen */ -public class ConfigServerLocation { - public final int restApiPort; +public class ConfigServerLocation extends AbstractComponent { + final int restApiPort; + // The client factory must be owned by a component as StateResource is instantiated per request + final VespaClientBuilderFactory clientBuilderFactory = new VespaClientBuilderFactory(); + + @Inject public ConfigServerLocation(ConfigserverConfig configServer) { restApiPort = configServer.httpport(); } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -22,4 +30,8 @@ public class ConfigServerLocation { return builder.toString(); } + @Override + public void deconstruct() { + clientBuilderFactory.close(); + } } diff --git a/configserver/src/main/java/com/yahoo/vespa/serviceview/StateResource.java b/configserver/src/main/java/com/yahoo/vespa/serviceview/StateResource.java index a6d4c229500..c58f3659ca5 100644 --- a/configserver/src/main/java/com/yahoo/vespa/serviceview/StateResource.java +++ b/configserver/src/main/java/com/yahoo/vespa/serviceview/StateResource.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.serviceview; +import ai.vespa.util.http.VespaClientBuilderFactory; import com.yahoo.container.jaxrs.annotation.Component; import com.yahoo.vespa.serviceview.bindings.ApplicationView; import com.yahoo.vespa.serviceview.bindings.ConfigClient; @@ -14,7 +15,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Context; @@ -28,8 +28,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import static java.util.Collections.singletonList; - /** * A web service to discover and proxy Vespa service state info. @@ -42,6 +40,7 @@ public class StateResource implements StateClient { private static final String USER_AGENT = "service-view-config-server-client"; private static final String SINGLE_API_LINK = "url"; + private final VespaClientBuilderFactory clientBuilderFactory; private final int restApiPort; private final String host; private final UriInfo uriInfo; @@ -58,6 +57,7 @@ public class StateResource implements StateClient { } public StateResource(@Component ConfigServerLocation configServer, @Context UriInfo ui) { + this.clientBuilderFactory = configServer.clientBuilderFactory; this.restApiPort = configServer.restApiPort; this.host = "localhost"; this.uriInfo = ui; @@ -278,11 +278,9 @@ public class StateResource implements StateClient { newUri.append(link.getRawPath()); } - private static Client client() { - return ClientBuilder.newBuilder() - .register((ClientRequestFilter) ctx -> ctx.getHeaders().put(HttpHeaders.USER_AGENT, - singletonList(USER_AGENT))) + private Client client() { + return clientBuilderFactory.newBuilder() + .register((ClientRequestFilter) ctx -> ctx.getHeaders().put(HttpHeaders.USER_AGENT, List.of(USER_AGENT))) .build(); } - } diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml index cfacd6ff8c9..97b2156e8ca 100644 --- a/configserver/src/main/resources/configserver-app/services.xml +++ b/configserver/src/main/resources/configserver-app/services.xml @@ -92,10 +92,6 @@ <handler id='com.yahoo.vespa.config.server.http.status.StatusHandler' bundle='configserver'> <binding>http://*/status</binding> </handler> - <handler id='com.yahoo.vespa.config.server.http.flags.FlagsHandler' bundle='configserver'> - <binding>http://*/flags/v1</binding> - <binding>http://*/flags/v1/*</binding> - </handler> <handler id='com.yahoo.vespa.config.server.http.v2.TenantHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/</binding> <binding>http://*/application/v2/tenant/*</binding> @@ -129,6 +125,7 @@ <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*</binding> + <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/metrics</binding> <binding>http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*</binding> <binding>http://*/application/v2/tenant/*/application/*</binding> <binding>http://*/application/v2/tenant/*/application/*/logs</binding> 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 e4ff8702ff1..6c144fe2f43 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 @@ -45,7 +45,6 @@ public class InjectedGlobalComponentRegistryTest { private SessionPreparer sessionPreparer; private ConfigserverConfig configserverConfig; private RpcServer rpcServer; - private SuperModelGenerationCounter generationCounter; private ConfigDefinitionRepo defRepo; private PermanentApplicationPackage permanentApplicationPackage; private HostRegistries hostRegistries; @@ -68,8 +67,10 @@ public class InjectedGlobalComponentRegistryTest { .configDefinitionsDir(temporaryFolder.newFolder("configdefinitions").getAbsolutePath())); sessionPreparer = new SessionTest.MockSessionPreparer(); rpcServer = new RpcServer(configserverConfig, null, Metrics.createTestMetrics(), - new HostRegistries(), new ConfigRequestHostLivenessTracker(), new FileServer(temporaryFolder.newFolder("filereferences")), new NoopRpcAuthorizer(), new RpcRequestHandlerProvider()); - generationCounter = new SuperModelGenerationCounter(curator); + new HostRegistries(), new ConfigRequestHostLivenessTracker(), + new FileServer(temporaryFolder.newFolder("filereferences")), + new NoopRpcAuthorizer(), new RpcRequestHandlerProvider()); + SuperModelGenerationCounter generationCounter = new SuperModelGenerationCounter(curator); defRepo = new StaticConfigDefinitionRepo(); permanentApplicationPackage = new PermanentApplicationPackage(configserverConfig); hostRegistries = new HostRegistries(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java index b83a46cb066..2df28f81e9e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java @@ -5,10 +5,10 @@ import com.yahoo.vespa.config.GenerationCounter; /** * @author Ulf Lilleengen - * @since 5. */ public class MemoryGenerationCounter implements GenerationCounter { - long value; + private long value; + @Override public long increment() { return ++value; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java index 88e602eed7c..f8777f4c477 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server; import static org.junit.Assert.*; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.ArrayList; import org.junit.Test; @@ -32,7 +33,7 @@ public class MiscTestCase { private static List<String> file2lines(File file) throws IOException { List<String> lines = new ArrayList<>(); - LineNumberReader in = new LineNumberReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); + LineNumberReader in = new LineNumberReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); String line; while ((line = in.readLine()) != null) { lines.add(line); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java index 99d0c6ea4da..6e9299c1a61 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java @@ -8,16 +8,13 @@ import java.util.Set; /** * @author Ulf Lilleengen - * @since 5.1.24 */ public class MockReloadHandler implements ReloadHandler { - public ApplicationSet current = null; public volatile ApplicationId lastRemoved = null; @Override public void reloadConfig(ApplicationSet application) { - this.current = application; } @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java index 986e9f5603d..2fd87161fd0 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java @@ -67,7 +67,7 @@ public class ModelFactoryRegistryTest { @Test(expected = UnknownVespaVersionException.class) public void testThatUnknownVersionGivesError() { - ModelFactoryRegistry registry = new ModelFactoryRegistry(Arrays.asList(new TestFactory(new Version(1, 2, 3)))); + ModelFactoryRegistry registry = new ModelFactoryRegistry(List.of(new TestFactory(new Version(1, 2, 3)))); registry.getFactory(new Version(3, 2, 1)); } @@ -75,7 +75,7 @@ public class ModelFactoryRegistryTest { private final Version version; - public TestFactory(Version version) { + TestFactory(Version version) { this.version = version; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java b/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java index 8424dccbedc..359c8562869 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java @@ -13,7 +13,6 @@ import java.util.Stack; * Allocates port ranges for all configserver tests. * * @author Ulf Lilleengen - * @since 5.1.26 */ public class PortRangeAllocator { private final static PortRange portRange = new PortRange(); @@ -33,7 +32,7 @@ public class PortRangeAllocator { private static final int first = 18651; private static final int last = 18899; // see: factory/doc/port-ranges - public PortRange() { + PortRange() { freePorts.addAll(ContiguousSet.create(Range.closed(first, last), DiscreteDomain.integers())); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java index c6f6be5fbab..b7486dc7951 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java @@ -54,7 +54,7 @@ public class SuperModelControllerTest { File testApp = new File("src/test/resources/deploy/app"); ApplicationId app = ApplicationId.from(TenantName.from("a"), ApplicationName.from("foo"), InstanceName.defaultName()); - models.put(app, new ApplicationInfo(app, 4l, new VespaModel(FilesApplicationPackage.fromFile(testApp)))); + models.put(app, new ApplicationInfo(app, 4L, new VespaModel(FilesApplicationPackage.fromFile(testApp)))); SuperModel superModel = new SuperModel(models); handler = new SuperModelController(new SuperModelConfigProvider(superModel, Zone.defaultZone(), new InMemoryFlagSource()), new TestConfigDefinitionRepo(), 2, new UncompressedConfigResponseFactory()); } @@ -94,9 +94,9 @@ public class SuperModelControllerTest { ApplicationId simple = applicationId("mysimpleapp", t1); ApplicationId advanced = applicationId("myadvancedapp", t1); ApplicationId tooAdvanced = applicationId("minetooadvancedapp", t2); - models.put(simple, createApplicationInfo(testApp1, simple, 4l)); - models.put(advanced, createApplicationInfo(testApp2, advanced, 4l)); - models.put(tooAdvanced, createApplicationInfo(testApp3, tooAdvanced, 4l)); + models.put(simple, createApplicationInfo(testApp1, simple, 4L)); + models.put(advanced, createApplicationInfo(testApp2, advanced, 4L)); + models.put(tooAdvanced, createApplicationInfo(testApp3, tooAdvanced, 4L)); SuperModel superModel = new SuperModel(models); SuperModelController han = new SuperModelController(new SuperModelConfigProvider(superModel, Zone.defaultZone(), new InMemoryFlagSource()), new TestConfigDefinitionRepo(), 2, new UncompressedConfigResponseFactory()); @@ -122,9 +122,9 @@ public class SuperModelControllerTest { ApplicationId simple = applicationId("mysimpleapp", t1); ApplicationId advanced = applicationId("myadvancedapp", t1); ApplicationId tooAdvanced = applicationId("minetooadvancedapp", t2); - models.put(simple, createApplicationInfo(testApp1, simple, 4l)); - models.put(advanced, createApplicationInfo(testApp2, advanced, 4l)); - models.put(tooAdvanced, createApplicationInfo(testApp3, tooAdvanced, 4l)); + models.put(simple, createApplicationInfo(testApp1, simple, 4L)); + models.put(advanced, createApplicationInfo(testApp2, advanced, 4L)); + models.put(tooAdvanced, createApplicationInfo(testApp3, tooAdvanced, 4L)); SuperModel superModel = new SuperModel(models); SuperModelController han = new SuperModelController(new SuperModelConfigProvider(superModel, Zone.defaultZone(), new InMemoryFlagSource()), new TestConfigDefinitionRepo(), 2, new UncompressedConfigResponseFactory()); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java index f7b900c8f02..dd2c3e07131 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java @@ -57,14 +57,14 @@ public class SuperModelRequestHandlerTest { assertNotNull(controller.getHandler()); long gen = counter.get(); - controller.reloadConfig(createApp(foo, 3l)); + controller.reloadConfig(createApp(foo, 3L)); assertNotNull(controller.getHandler()); assertThat(controller.getHandler().getGeneration(), is(gen + 1)); - controller.reloadConfig(createApp(foo, 4l)); + controller.reloadConfig(createApp(foo, 4L)); assertThat(controller.getHandler().getGeneration(), is(gen + 2)); // Test that a new app is used when there already exist an application with the same id - assertThat(controller.getHandler().getSuperModel().applicationModels().get(foo).getGeneration(), is(4l)); - controller.reloadConfig(createApp(bar, 2l)); + assertThat(controller.getHandler().getSuperModel().applicationModels().get(foo).getGeneration(), is(4L)); + controller.reloadConfig(createApp(bar, 2L)); assertThat(controller.getHandler().getGeneration(), is(gen + 3)); } @@ -75,9 +75,9 @@ public class SuperModelRequestHandlerTest { ApplicationId baz = applicationId("b", "baz"); long gen = counter.get(); - controller.reloadConfig(createApp(foo, 3l)); - controller.reloadConfig(createApp(bar, 30l)); - controller.reloadConfig(createApp(baz, 9l)); + controller.reloadConfig(createApp(foo, 3L)); + controller.reloadConfig(createApp(bar, 30L)); + controller.reloadConfig(createApp(baz, 9L)); assertThat(controller.getHandler().getGeneration(), is(gen + 3)); assertThat(controller.getHandler().getSuperModel().applicationModels().size(), is(3)); assertEquals(Arrays.asList(foo, bar, baz), new ArrayList<>(controller.getHandler().getSuperModel().applicationModels().keySet())); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java index 070201a4369..db9906b9e02 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java @@ -24,16 +24,16 @@ public class TimeoutBudgetTest { ManualClock clock = new ManualClock(); TimeoutBudget budget = new TimeoutBudget(clock, Duration.ofMillis(7)); - assertThat(budget.timeLeft().toMillis(), is(7l)); + assertThat(budget.timeLeft().toMillis(), is(7L)); clock.advance(Duration.ofMillis(1)); - assertThat(budget.timeLeft().toMillis(), is(6l)); + assertThat(budget.timeLeft().toMillis(), is(6L)); clock.advance(Duration.ofMillis(5)); - assertThat(budget.timeLeft().toMillis(), is(1l)); - assertThat(budget.timeLeft().toMillis(), is(1l)); + assertThat(budget.timeLeft().toMillis(), is(1L)); + assertThat(budget.timeLeft().toMillis(), is(1L)); clock.advance(Duration.ofMillis(1)); - assertThat(budget.timeLeft().toMillis(), is(0l)); + assertThat(budget.timeLeft().toMillis(), is(0L)); clock.advance(Duration.ofMillis(5)); - assertThat(budget.timeLeft().toMillis(), is(0l)); + assertThat(budget.timeLeft().toMillis(), is(0L)); clock.advance(Duration.ofMillis(1)); assertThat(budget.timesUsed(), is("[0 ms, 1 ms, 5 ms, 0 ms, 1 ms, 5 ms, total: 13 ms]")); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java index 284bb716a6e..a562f89c7ec 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.application; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Optional; import com.yahoo.config.provision.ApplicationId; @@ -18,22 +19,21 @@ import static org.junit.Assert.assertEquals; public class ApplicationMapperTest { - ApplicationId appId; - ApplicationMapper applicationMapper; - ArrayList<Version> vespaVersions = new ArrayList<>(); - ArrayList<Application> applications = new ArrayList<>(); + private ApplicationId appId; + private ApplicationMapper applicationMapper; + private ArrayList<Version> vespaVersions = new ArrayList<>(); + private ArrayList<Application> applications = new ArrayList<>(); @Before public void setUp() { applicationMapper = new ApplicationMapper(); appId = new ApplicationId.Builder() - .tenant("test").applicationName("test").instanceName("test").build(); - vespaVersions.add(Version.fromString("1.2.3")); - vespaVersions.add(Version.fromString("1.2.4")); - vespaVersions.add(Version.fromString("1.2.5")); - applications.add(new Application(new ModelStub(), null, 0, false, vespaVersions.get(0), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())); - applications.add(new Application(new ModelStub(), null, 0, false, vespaVersions.get(1), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())); - applications.add(new Application(new ModelStub(), null, 0, false, vespaVersions.get(2), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())); + .tenant("test") + .applicationName("test") + .instanceName("test") + .build(); + vespaVersions.addAll(List.of(Version.fromString("1.2.3"), Version.fromString("1.2.4"), Version.fromString("1.2.5"))); + applications.addAll(List.of(createApplication(vespaVersions.get(0)), createApplication(vespaVersions.get(1)), createApplication(vespaVersions.get(2)))); } @Test @@ -53,7 +53,6 @@ public class ApplicationMapperTest { @Test (expected = VersionDoesNotExistException.class) public void testGetForVersionThrows() { applicationMapper.register(appId, ApplicationSet.fromList(Arrays.asList(applications.get(0), applications.get(2)))); - applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1)), Instant.now()); } @@ -62,9 +61,16 @@ public class ApplicationMapperTest { applicationMapper.register(appId, ApplicationSet.fromSingle(applications.get(0))); applicationMapper.getForVersion(new ApplicationId.Builder() - .tenant("different").applicationName("different").instanceName("different").build(), + .tenant("different") + .applicationName("different") + .instanceName("different") + .build(), Optional.of(vespaVersions.get(1)), Instant.now()); } + private Application createApplication(Version version) { + return new Application(new ModelStub(), null, 0, false, version, MetricUpdater.createTestUpdater(), ApplicationId.defaultId()); + } + } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java index 8ee6a82bd1d..cf1e00674cb 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java @@ -20,18 +20,14 @@ import static org.junit.Assert.assertEquals; */ public class ApplicationSetTest { - ApplicationSet applicationSet; - List<Version> vespaVersions = new ArrayList<>(); - List<Application> applications = new ArrayList<>(); + private ApplicationSet applicationSet; + private List<Version> vespaVersions = new ArrayList<>(); + private List<Application> applications = new ArrayList<>(); @Before public void setUp() { - vespaVersions.add(Version.fromString("1.2.3")); - vespaVersions.add(Version.fromString("1.2.4")); - vespaVersions.add(Version.fromString("1.2.5")); - applications.add(new Application(new ModelStub(), null, 0, false, vespaVersions.get(0), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())); - applications.add(new Application(new ModelStub(), null, 0, false, vespaVersions.get(1), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())); - applications.add(new Application(new ModelStub(), null, 0, false, vespaVersions.get(2), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())); + vespaVersions.addAll(List.of(Version.fromString("1.2.3"), Version.fromString("1.2.4"), Version.fromString("1.2.5"))); + applications.addAll(List.of(createApplication(vespaVersions.get(0)), createApplication(vespaVersions.get(1)), createApplication(vespaVersions.get(2)))); } @Test @@ -54,4 +50,7 @@ public class ApplicationSetTest { applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1)), Instant.now()); } + private Application createApplication(Version version) { + return new Application(new ModelStub(), null, 0, false, version, MetricUpdater.createTestUpdater(), ApplicationId.defaultId()); + } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java index cc30ef09878..405fff3e190 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java @@ -55,7 +55,7 @@ public class ApplicationTest { ServerCache cache = new ServerCache(); Version vespaVersion = new Version(1, 2, 3); Application app = new Application(new ModelStub(), cache, 1337L, false, vespaVersion, MetricUpdater.createTestUpdater(), appId); - assertThat(app.getApplicationGeneration(), is(1337l)); + assertThat(app.getApplicationGeneration(), is(1337L)); assertNotNull(app.getModel()); assertThat(app.getCache(), is(cache)); assertThat(app.getId().application().value(), is("foobar")); @@ -107,7 +107,7 @@ public class ApplicationTest { @Test public void require_that_known_config_defs_are_found() { - handler.resolveConfig(createSimpleConfigRequest(emptySchema)); + handler.resolveConfig(createSimpleConfigRequest()); } @Test @@ -119,7 +119,7 @@ public class ApplicationTest { @Test public void require_that_non_existent_fields_in_schema_is_skipped() { // Ask for config without schema and check that we get correct default value back - List<String> payload = handler.resolveConfig(createSimpleConfigRequest(emptySchema)).getLegacyPayload(); + List<String> payload = handler.resolveConfig(createSimpleConfigRequest()).getLegacyPayload(); assertThat(payload.get(0), is("boolval false")); // Ask for config with wrong schema String[] schema = new String[1]; @@ -138,19 +138,18 @@ public class ApplicationTest { assertTrue(response == cached_response); } - private static GetConfigRequest createRequest(String name, String namespace, String defMd5, String[] schema, String configId) { + private static GetConfigRequest createRequest(String name, String namespace, String defMd5, String[] schema) { Request request = JRTClientConfigRequestV3. - createWithParams(new ConfigKey<>(name, configId, namespace, defMd5, null), DefContent.fromArray(schema), + createWithParams(new ConfigKey<>(name, "admin/model", namespace, defMd5, null), DefContent.fromArray(schema), "fromHost", "", 0, 100, Trace.createDummy(), CompressionType.UNCOMPRESSED, Optional.empty()).getRequest(); return JRTServerConfigRequestV3.createFromRequest(request); } - private static GetConfigRequest createRequest(String name, String namespace, String defMd5, String[] schema) { - return createRequest(name, namespace, defMd5, schema, "admin/model"); - } - - private static GetConfigRequest createSimpleConfigRequest(String[] schema) { - return createRequest(SimpletypesConfig.CONFIG_DEF_NAME, SimpletypesConfig.CONFIG_DEF_NAMESPACE, SimpletypesConfig.CONFIG_DEF_MD5, schema); + private static GetConfigRequest createSimpleConfigRequest() { + return createRequest(SimpletypesConfig.CONFIG_DEF_NAME, + SimpletypesConfig.CONFIG_DEF_NAMESPACE, + SimpletypesConfig.CONFIG_DEF_MD5, + ApplicationTest.emptySchema); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java index 496da2cf809..016192e3281 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/CompressedApplicationInputStreamTest.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.config.server.application; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; -import com.yahoo.vespa.config.server.application.CompressedApplicationInputStream; import org.apache.commons.compress.archivers.ArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; @@ -22,6 +21,7 @@ import java.util.zip.GZIPOutputStream; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -30,13 +30,13 @@ import static org.junit.Assert.assertTrue; */ public class CompressedApplicationInputStreamTest { - static void writeFileToTar(ArchiveOutputStream taos, File file) throws IOException { + private static void writeFileToTar(ArchiveOutputStream taos, File file) throws IOException { taos.putArchiveEntry(taos.createArchiveEntry(file, file.getName())); ByteStreams.copy(new FileInputStream(file), taos); taos.closeArchiveEntry(); } - public static File createArchiveFile(ArchiveOutputStream taos, File outFile) throws IOException { + private static File createArchiveFile(ArchiveOutputStream taos, File outFile) throws IOException { File app = new File("src/test/resources/deploy/validapp"); writeFileToTar(taos, new File(app, "services.xml")); writeFileToTar(taos, new File(app, "hosts.xml")); @@ -51,14 +51,15 @@ public class CompressedApplicationInputStreamTest { return createArchiveFile(archiveOutputStream, outFile); } - public static File createZipFile() throws IOException { + private static File createZipFile() throws IOException { File outFile = File.createTempFile("testapp", ".tar.gz"); ArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(new FileOutputStream(outFile)); return createArchiveFile(archiveOutputStream, outFile); } - void assertTestApp(File outApp) { + private void assertTestApp(File outApp) { String [] files = outApp.list(); + assertNotNull(files); assertThat(files.length, is(3)); assertThat(Arrays.asList(files), containsInAnyOrder(ImmutableList.of(is("hosts.xml"), is("services.xml"), is("deployment.xml")))); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java index 871182d75d9..71ae6955e56 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ConfigConvergenceCheckerTest.java @@ -40,6 +40,7 @@ import static org.junit.Assert.assertTrue; */ public class ConfigConvergenceCheckerTest { + private static final Duration clientTimeout = Duration.ofSeconds(10); private final TenantName tenant = TenantName.from("mytenant"); private final ApplicationId appId = ApplicationId.from(tenant, ApplicationName.from("myapp"), InstanceName.from("myinstance")); @@ -76,7 +77,7 @@ public class ConfigConvergenceCheckerTest { String serviceName = hostAndPort(this.service); URI requestUrl = testServer().resolve("/serviceconverge/" + serviceName); wireMock.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(okJson("{\"config\":{\"generation\":3}}"))); - HttpResponse serviceResponse = checker.checkService(application, hostAndPort(this.service), requestUrl, Duration.ofSeconds(5)); + HttpResponse serviceResponse = checker.checkService(application, hostAndPort(this.service), requestUrl, clientTimeout); assertResponse("{\n" + " \"url\": \"" + requestUrl.toString() + "\",\n" + " \"host\": \"" + hostAndPort(this.service) + "\",\n" + @@ -91,8 +92,7 @@ public class ConfigConvergenceCheckerTest { { // Missing service String serviceName = "notPresent:1337"; URI requestUrl = testServer().resolve("/serviceconverge/" + serviceName); - HttpResponse response = checker.checkService(application, "notPresent:1337", requestUrl, - Duration.ofSeconds(5)); + HttpResponse response = checker.checkService(application, "notPresent:1337", requestUrl,clientTimeout); assertResponse("{\n" + " \"url\": \"" + requestUrl.toString() + "\",\n" + " \"host\": \"" + serviceName + "\",\n" + @@ -111,7 +111,7 @@ public class ConfigConvergenceCheckerTest { URI requestUrl = testServer().resolve("/serviceconverge"); URI serviceUrl = testServer().resolve("/serviceconverge/" + serviceName); wireMock.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(okJson("{\"config\":{\"generation\":3}}"))); - HttpResponse response = checker.servicesToCheck(application, requestUrl, Duration.ofSeconds(5)); + HttpResponse response = checker.servicesToCheck(application, requestUrl, clientTimeout); assertResponse("{\n" + " \"services\": [\n" + " {\n" + @@ -148,7 +148,7 @@ public class ConfigConvergenceCheckerTest { URI requestUrl = testServer().resolve("/serviceconverge"); URI serviceUrl = testServer().resolve("/serviceconverge/" + hostAndPort(service)); URI serviceUrl2 = testServer().resolve("/serviceconverge/" + hostAndPort(service2)); - HttpResponse response = checker.servicesToCheck(application, requestUrl, Duration.ofSeconds(5)); + HttpResponse response = checker.servicesToCheck(application, requestUrl, clientTimeout); assertResponse("{\n" + " \"services\": [\n" + " {\n" + @@ -180,16 +180,14 @@ public class ConfigConvergenceCheckerTest { public void service_convergence_timeout() { URI requestUrl = testServer().resolve("/serviceconverge"); wireMock.stubFor(get(urlEqualTo("/state/v1/config")).willReturn(aResponse() - .withFixedDelay((int) Duration.ofSeconds(10).toMillis()) + .withFixedDelay((int) clientTimeout.plus(Duration.ofSeconds(1)).toMillis()) .withBody("response too slow"))); HttpResponse response = checker.checkService(application, hostAndPort(service), requestUrl, Duration.ofMillis(1)); // Message contained in a SocketTimeoutException may differ across platforms, so we do a partial match of the response here - assertResponse((responseBody) -> { - assertTrue("Response matches", responseBody.startsWith( - "{\"url\":\"" + requestUrl.toString() + "\",\"host\":\"" + hostAndPort(requestUrl) + - "\",\"wantedGeneration\":3,\"error\":\"java.net.SocketTimeoutException") && - responseBody.endsWith("\"}")); - }, 404, response); + assertResponse((responseBody) -> assertTrue("Response matches", responseBody.startsWith( + "{\"url\":\"" + requestUrl.toString() + "\",\"host\":\"" + hostAndPort(requestUrl) + + "\",\"wantedGeneration\":3,\"error\":\"java.net.SocketTimeoutException") && + responseBody.endsWith("\"}")), 404, response); } private URI testServer() { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/FileDistributionStatusTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/FileDistributionStatusTest.java index 0c27066dd6b..89392d799ba 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/FileDistributionStatusTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/FileDistributionStatusTest.java @@ -170,7 +170,7 @@ public class FileDistributionStatusTest { appId); } - HttpResponse getStatus(FileDistributionStatus fileDistributionStatus, Application application) { + private HttpResponse getStatus(FileDistributionStatus fileDistributionStatus, Application application) { return fileDistributionStatus.status(application, timeout); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java index 433a63f4ad2..0da96f9f01d 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MockModel.java @@ -45,6 +45,7 @@ public class MockModel implements Model { return new HostInfo(hostname, Arrays.asList(container, serviceNoStatePort)); } + // TODO: Move to caller static MockModel createClusterController(String hostname, int statePort) { ServiceInfo container = createServiceInfo( hostname, diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java index aa157366a60..66d113afbe6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/OrchestratorMock.java @@ -4,7 +4,6 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.Orchestrator; -import com.yahoo.vespa.orchestrator.model.NodeGroup; import com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus; import com.yahoo.vespa.orchestrator.status.HostStatus; 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 e3335dded4c..33932a678b7 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 @@ -69,11 +69,11 @@ public class TenantApplicationsTest { TenantApplications repo = createZKAppRepo(); ApplicationId myapp = createApplicationId("myapp"); repo.createApplication(myapp); - repo.createPutTransaction(myapp, 3l).commit(); + repo.createPutTransaction(myapp, 3).commit(); String path = TenantRepository.getApplicationsPath(tenantName).append(myapp.serializedForm()).getAbsolute(); assertNotNull(curatorFramework.checkExists().forPath(path)); assertThat(Utf8.toString(curatorFramework.getData().forPath(path)), is("3")); - repo.createPutTransaction(myapp, 5l).commit(); + repo.createPutTransaction(myapp, 5).commit(); assertNotNull(curatorFramework.checkExists().forPath(path)); assertThat(Utf8.toString(curatorFramework.getData().forPath(path)), is("5")); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java index 4a23d76b76e..2566b1029a8 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java @@ -11,7 +11,6 @@ import java.util.List; /** * @author geirst - * @since 5.44 */ public class ConfigChangeActionsBuilder { @@ -25,15 +24,15 @@ public class ConfigChangeActionsBuilder { public ConfigChangeActionsBuilder restart(String message, String clusterName, String clusterType, String serviceType, String serviceName) { actions.add(new MockRestartAction(message, - Arrays.asList(createService(clusterName, clusterType, serviceType, serviceName)))); + List.of(createService(clusterName, clusterType, serviceType, serviceName)))); return this; } - public ConfigChangeActionsBuilder refeed(String name, boolean allowed, String message, String documentType, String clusterName, String serviceName) { + ConfigChangeActionsBuilder refeed(String name, boolean allowed, String message, String documentType, String clusterName, String serviceName) { actions.add(new MockRefeedAction(name, allowed, message, - Arrays.asList(createService(clusterName, "myclustertype", "myservicetype", serviceName)), documentType)); + List.of(createService(clusterName, "myclustertype", "myservicetype", serviceName)), documentType)); return this; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java index 64e3aceee8d..639e4a6fee3 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java @@ -15,7 +15,7 @@ public abstract class MockConfigChangeAction implements ConfigChangeAction { private final String message; private final List<ServiceInfo> services; - protected MockConfigChangeAction(String message, List<ServiceInfo> services) { + MockConfigChangeAction(String message, List<ServiceInfo> services) { this.message = message; this.services = services; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java index 6f21682981a..7235b8905c5 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.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.configchange; +import com.yahoo.config.model.api.ServiceInfo; import org.junit.Test; import java.util.List; @@ -13,7 +14,6 @@ import static com.yahoo.vespa.config.server.configchange.Utils.*; /** * @author geirst - * @since 5.44 */ public class RefeedActionsTest { @@ -21,7 +21,7 @@ public class RefeedActionsTest { StringBuilder builder = new StringBuilder(); builder.append(entry.getDocumentType() + "." + entry.getClusterName() + ":"); builder.append(entry.getServices().stream(). - map(service -> service.getServiceName()). + map(ServiceInfo::getServiceName). sorted(). collect(Collectors.joining(",", "[", "]"))); builder.append(entry.getMessages().stream(). diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java index 4c937061733..ee0180802af 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.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.configchange; +import com.yahoo.config.model.api.ServiceInfo; import org.junit.Test; import java.util.List; @@ -13,7 +14,6 @@ import static com.yahoo.vespa.config.server.configchange.Utils.*; /** * @author geirst - * @since 5.44 */ public class RestartActionsTest { @@ -21,7 +21,7 @@ public class RestartActionsTest { StringBuilder builder = new StringBuilder(); builder.append(entry.getClusterType() + "." + entry.getClusterName() + "." + entry.getServiceType() + ":"); builder.append(entry.getServices().stream(). - map(service -> service.getServiceName()). + map(ServiceInfo::getServiceName). sorted(). collect(Collectors.joining(",", "[", "]"))); builder.append(entry.getMessages().stream(). diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLoggerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLoggerTest.java index d5092adb90b..d321edefe67 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLoggerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployHandlerLoggerTest.java @@ -8,7 +8,6 @@ import com.yahoo.slime.Cursor; import com.yahoo.slime.JsonFormat; import com.yahoo.slime.Slime; -import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger; import org.junit.Test; import java.io.ByteArrayOutputStream; @@ -19,7 +18,6 @@ import static org.junit.Assert.assertTrue; /** * @author Ulf Lilleengen - * @since 5.1 */ public class DeployHandlerLoggerTest { @Test diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java index b363c749212..74d3fd4ec74 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java @@ -366,7 +366,7 @@ public class HostedDeployTest { @Override public ModelCreateResult createAndValidateModel(ModelContext modelContext, ValidationParameters validationParameters) { ModelCreateResult result = super.createAndValidateModel(modelContext, validationParameters); - return new ModelCreateResult(result.getModel(), Arrays.asList(action)); + return new ModelCreateResult(result.getModel(), List.of(action)); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java index da387eb569a..967e2321b95 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java @@ -13,8 +13,6 @@ import java.util.Optional; */ public class MockDeployer implements com.yahoo.config.provision.Deployer { - public ApplicationId lastDeployed; - @Override public Optional<Deployment> deployFromLocalActive(ApplicationId application) { return deployFromLocalActive(application, Duration.ofSeconds(60)); @@ -27,7 +25,6 @@ public class MockDeployer implements com.yahoo.config.provision.Deployer { @Override public Optional<Deployment> deployFromLocalActive(ApplicationId application, Duration timeout) { - lastDeployed = application; return Optional.empty(); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java index adf3ba19fe3..49327f984b7 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java @@ -9,7 +9,6 @@ import com.yahoo.component.Version; import org.junit.Test; import java.time.Clock; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java index 918670d71f2..d01ebad8c26 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java @@ -80,7 +80,7 @@ public class ZooKeeperClientTest { } @Test - public void testInitZooKeeper() throws IOException { + public void testInitZooKeeper() { ConfigCurator zk = ConfigCurator.create(new MockCurator()); BaseDeployLogger logger = new BaseDeployLogger(); long generation = 1L; @@ -88,8 +88,8 @@ public class ZooKeeperClientTest { zooKeeperClient.setupZooKeeper(); String appPath = "/"; assertThat(zk.getChildren(appPath).size(), is(1)); - assertTrue(zk.exists("/" + String.valueOf(generation))); - String currentAppPath = appPath + String.valueOf(generation); + assertTrue(zk.exists("/" + generation)); + String currentAppPath = appPath + generation; assertTrue(zk.exists(currentAppPath, ConfigCurator.DEFCONFIGS_ZK_SUBPATH.replaceFirst("/", ""))); assertThat(zk.getChildren(currentAppPath).size(), is(4)); } @@ -101,14 +101,14 @@ public class ZooKeeperClientTest { List<String> children = zk.getChildren(defsPath); assertEquals(defsPath + " children", 2, children.size()); Collections.sort(children); - assertThat(children.get(0), is("a.b.test2,")); + assertThat(children.get(0), is("a.b.test2")); assertTrue(zk.exists(appPath, ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH.replaceFirst("/", ""))); String userDefsPath = appPath + ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH; children = zk.getChildren(userDefsPath); assertThat(children.size(), is(2)); Collections.sort(children); - assertThat(children.get(0), is("a.b.test2,")); + assertThat(children.get(0), is("a.b.test2")); } // TODO: Evaluate if we want this or not @@ -140,9 +140,9 @@ public class ZooKeeperClientTest { assertTrue(metaData.isInternalRedeploy()); assertThat(metaData.getDeployedByUser(), is("foo")); assertThat(metaData.getDeployPath(), is("/bar/baz")); - assertThat(metaData.getDeployTimestamp(), is(1345l)); - assertThat(metaData.getGeneration(), is(3l)); - assertThat(metaData.getPreviousActiveGeneration(), is(2l)); + assertThat(metaData.getDeployTimestamp(), is(1345L)); + assertThat(metaData.getGeneration(), is(3L)); + assertThat(metaData.getPreviousActiveGeneration(), is(2L)); } @Test diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java index d00c0b8dd32..4825ccc1328 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java @@ -8,7 +8,6 @@ import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.component.Version; import com.yahoo.io.IOUtils; import com.yahoo.path.Path; -import com.yahoo.prelude.semantics.parser.ParseException; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import org.junit.Rule; @@ -25,7 +24,6 @@ import static org.junit.Assert.fail; /** * @author Ulf Lilleengen - * @since 5.1 */ public class ZooKeeperDeployerTest { @@ -34,7 +32,7 @@ public class ZooKeeperDeployerTest { private static final String defFile = "test2.def"; @Test - public void require_that_deployer_is_initialized() throws IOException, ParseException { + public void require_that_deployer_is_initialized() throws IOException { ConfigCurator zkfacade = ConfigCurator.create(new MockCurator()); File serverdbDir = folder.newFolder("serverdb"); File defsDir = new File(serverdbDir, "serverdefs"); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/host/HostRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/host/HostRegistryTest.java index b2335165105..63dfb1d01bd 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/host/HostRegistryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/host/HostRegistryTest.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.config.server.host; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -20,12 +19,12 @@ public class HostRegistryTest { public void old_hosts_are_removed() { HostRegistry<String> reg = new HostRegistry<>(); assertNull(reg.getKeyForHost("foo.com")); - reg.update("fookey", Arrays.asList("foo.com", "bar.com", "baz.com")); + reg.update("fookey", List.of("foo.com", "bar.com", "baz.com")); assertGetKey(reg, "foo.com", "fookey"); assertGetKey(reg, "bar.com", "fookey"); assertGetKey(reg, "baz.com", "fookey"); assertThat(reg.getAllHosts().size(), is(3)); - reg.update("fookey", Arrays.asList("bar.com", "baz.com")); + reg.update("fookey", List.of("bar.com", "baz.com")); assertNull(reg.getKeyForHost("foo.com")); assertGetKey(reg, "bar.com", "fookey"); assertGetKey(reg, "baz.com", "fookey"); @@ -41,8 +40,8 @@ public class HostRegistryTest { @Test public void multiple_keys_are_handled() { HostRegistry<String> reg = new HostRegistry<>(); - reg.update("fookey", Arrays.asList("foo.com", "bar.com")); - reg.update("barkey", Arrays.asList("baz.com", "quux.com")); + reg.update("fookey", List.of("foo.com", "bar.com")); + reg.update("barkey", List.of("baz.com", "quux.com")); assertGetKey(reg, "foo.com", "fookey"); assertGetKey(reg, "bar.com", "fookey"); assertGetKey(reg, "baz.com", "barkey"); @@ -52,22 +51,22 @@ public class HostRegistryTest { @Test(expected = IllegalArgumentException.class) public void keys_cannot_overlap() { HostRegistry<String> reg = new HostRegistry<>(); - reg.update("fookey", Arrays.asList("foo.com", "bar.com")); - reg.update("barkey", Arrays.asList("bar.com", "baz.com")); + reg.update("fookey", List.of("foo.com", "bar.com")); + reg.update("barkey", List.of("bar.com", "baz.com")); } @Test public void all_hosts_are_returned() { HostRegistry<String> reg = new HostRegistry<>(); - reg.update("fookey", Arrays.asList("foo.com", "bar.com")); - reg.update("barkey", Arrays.asList("baz.com", "quux.com")); + reg.update("fookey", List.of("foo.com", "bar.com")); + reg.update("barkey", List.of("baz.com", "quux.com")); assertThat(reg.getAllHosts().size(), is(4)); } @Test public void ensure_that_collection_is_copied() { HostRegistry<String> reg = new HostRegistry<>(); - List<String> hosts = new ArrayList<>(Arrays.asList("foo.com", "bar.com", "baz.com")); + List<String> hosts = new ArrayList<>(List.of("foo.com", "bar.com", "baz.com")); reg.update("fookey", hosts); assertThat(reg.getHostsForKey("fookey").size(), is(3)); hosts.remove(2); @@ -77,10 +76,10 @@ public class HostRegistryTest { @Test public void ensure_that_underlying_hosts_do_not_change() { HostRegistry<String> reg = new HostRegistry<>(); - reg.update("fookey", new ArrayList<>(Arrays.asList("foo.com", "bar.com", "baz.com"))); + reg.update("fookey", List.of("foo.com", "bar.com", "baz.com")); Collection<String> hosts = reg.getAllHosts(); assertThat(hosts.size(), is(3)); - reg.update("fookey", new ArrayList<>(Arrays.asList("foo.com"))); + reg.update("fookey", List.of("foo.com")); assertThat(hosts.size(), is(3)); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java index 3415facd714..20e52263350 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java @@ -11,10 +11,8 @@ import static org.junit.Assert.assertThat; import java.io.IOException; import java.util.Arrays; import java.util.Collection; -import javax.annotation.Nullable; import org.junit.Test; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Collections2; import com.yahoo.container.jdisc.HttpResponse; @@ -37,7 +35,7 @@ public abstract class ContentHandlerTestBase extends SessionHandlerTest { } @Test - public void require_that_nonexistant_file_returns_not_found() throws IOException { + public void require_that_nonexistant_file_returns_not_found() { HttpResponse response = doRequest(HttpRequest.Method.GET, "/test2.txt"); assertNotNull(response); assertThat(response.getStatus(), is(NOT_FOUND)); @@ -88,12 +86,7 @@ public abstract class ContentHandlerTestBase extends SessionHandlerTest { protected abstract HttpResponse doRequest(HttpRequest.Method method, String path); private String generateResultArray(String... files) { - Collection<String> output = Collections2.transform(Arrays.asList(files), new Function<String, String>() { - @Override - public String apply(@Nullable String input) { - return "\"" + baseUrl + input + "\""; - } - }); + Collection<String> output = Collections2.transform(Arrays.asList(files), input -> "\"" + baseUrl + input + "\""); StringBuilder sb = new StringBuilder(); sb.append("["); sb.append(Joiner.on(",").join(output)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java index 62ec451107d..d28aa804c6f 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.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.http; -import java.io.IOException; - import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.vespa.config.ConfigKey; @@ -17,7 +15,6 @@ import static org.junit.Assert.assertTrue; /** * @author Ulf Lilleengen - * @since 5.1 */ public class HttpConfigRequestTest { @Test @@ -39,7 +36,7 @@ public class HttpConfigRequestTest { } @Test - public void require_that_request_can_be_created_with_advanced_uri() throws IOException { + public void require_that_request_can_be_created_with_advanced_uri() { HttpConfigRequest.createFromRequestV1(HttpRequest.createTestRequest( "http://example.yahoo.com:19071/config/v1/vespa.config.cloud.sentinel/host-01.example.yahoo.com", GET)); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java index 54a3db3d94d..56391127b62 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java @@ -12,7 +12,6 @@ import static com.yahoo.jdisc.http.HttpResponse.Status.*; /** * @author Ulf Lilleengen - * @since 5.1 */ public class HttpErrorResponseTest { @Test @@ -29,7 +28,7 @@ public class HttpErrorResponseTest { } @Test - public void testThatHttpErrorResponseHasJsonContentType() throws IOException { + public void testThatHttpErrorResponseHasJsonContentType() { HttpErrorResponse response = HttpErrorResponse.badRequest("Error doing something"); assertThat(response.getContentType(), is("application/json")); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java index a1fbbb57ce2..3ae98c1b8f2 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java @@ -6,7 +6,6 @@ import com.yahoo.config.codegen.DefParser; import com.yahoo.config.codegen.InnerCNode; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.container.logging.AccessLog; import com.yahoo.text.StringUtilities; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; @@ -20,17 +19,14 @@ import java.io.IOException; import java.io.StringReader; import java.util.Collections; import java.util.HashSet; -import java.util.concurrent.Executor; import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static com.yahoo.jdisc.http.HttpRequest.Method.GET; import static com.yahoo.jdisc.http.HttpResponse.Status.*; - /** * @author Ulf Lilleengen - * @since 5.1 */ public class HttpGetConfigHandlerTest { private static final String configUri = "http://yahoo.com:8080/config/v1/foo.bar/myid"; @@ -41,12 +37,10 @@ public class HttpGetConfigHandlerTest { @Before public void setUp() { mockRequestHandler = new MockRequestHandler(); - mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{ + mockRequestHandler.setAllConfigs(new HashSet<>() {{ add(new ConfigKey<>("bar", "myid", "foo")); - }} ); - handler = new HttpGetConfigHandler( - HttpGetConfigHandler.testOnlyContext(), - mockRequestHandler); + }} ); + handler = new HttpGetConfigHandler(HttpGetConfigHandler.testOnlyContext(), mockRequestHandler); } @Test diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java index e8d4373842d..e2bd8a120e0 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java @@ -46,7 +46,7 @@ public class HttpHandlerTest { private static class HttpTestHandler extends HttpHandler { private RuntimeException exception; - public HttpTestHandler(RuntimeException exception) { + HttpTestHandler(RuntimeException exception) { super(HttpHandler.testOnlyContext()); this.exception = exception; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java index 25b9e66ceb3..9113978d58b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.config.server.http; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.container.logging.AccessLog; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.server.rpc.MockRequestHandler; import com.yahoo.vespa.config.server.http.HttpListConfigsHandler.ListConfigsResponse; @@ -13,7 +12,6 @@ import org.junit.Test; import java.io.IOException; import java.util.*; -import java.util.concurrent.Executor; import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; @@ -23,7 +21,6 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.GET; /** * @author Ulf Lilleengen - * @since 5.1 */ public class HttpListConfigsHandlerTest { @@ -34,9 +31,9 @@ public class HttpListConfigsHandlerTest { @Before public void setUp() { mockRequestHandler = new MockRequestHandler(); - mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{ + mockRequestHandler.setAllConfigs(new HashSet<>() {{ add(new ConfigKey<>("bar", "conf/id/", "foo")); - }} ); + }} ); HttpListConfigsHandler.Context ctx = HttpListConfigsHandler.testOnlyContext(); handler = new HttpListConfigsHandler(ctx, mockRequestHandler); namedHandler = new HttpListNamedConfigsHandler(ctx, mockRequestHandler); @@ -51,14 +48,14 @@ public class HttpListConfigsHandlerTest { @Test public void require_that_named_handler_can_be_created() throws IOException { HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v1/foo.bar/conf/id/", GET); - req.getJDiscRequest().parameters().put("http.path", Arrays.asList("foo.bar")); + req.getJDiscRequest().parameters().put("http.path", List.of("foo.bar")); HttpResponse response = namedHandler.handle(req); assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}")); } @Test public void require_child_listings_correct() { - Set<ConfigKey<?>> keys = new LinkedHashSet<ConfigKey<?>>() {{ + Set<ConfigKey<?>> keys = new LinkedHashSet<>() {{ add(new ConfigKey<>("name1", "id/1", "ns1")); add(new ConfigKey<>("name1", "id/1", "ns1")); add(new ConfigKey<>("name1", "id/2", "ns1")); @@ -74,7 +71,7 @@ public class HttpListConfigsHandlerTest { @Test public void require_url_building_and_mimetype_correct() { - HttpListConfigsHandler.ListConfigsResponse resp = new ListConfigsResponse(new HashSet<ConfigKey<?>>(), null, "http://foo.com/config/v1/", true); + HttpListConfigsHandler.ListConfigsResponse resp = new ListConfigsResponse(new HashSet<>(), null, "http://foo.com/config/v1/", true); assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), true), "http://foo.com/config/v1/mynamespace.myconfig/my/id"); assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), false), "http://foo.com/config/v1/mynamespace.myconfig/my/id/"); assertEquals(resp.getContentType(), "application/json"); @@ -96,7 +93,7 @@ public class HttpListConfigsHandlerTest { @Test public void require_correct_error_response_on_no_model() throws IOException { - mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>()); + mockRequestHandler.setAllConfigs(new HashSet<>()); HttpResponse response = namedHandler.handle(HttpRequest.createTestRequest("http://yahoo.com:8080/config/v1/foo.bar/myid/", GET)); HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java index 0381af57cc3..9a326a18dd5 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java @@ -6,7 +6,6 @@ import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.application.provider.FilesApplicationPackage; -import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; @@ -33,14 +32,13 @@ import com.yahoo.vespa.config.server.session.PrepareParams; import com.yahoo.vespa.config.server.session.Session; import com.yahoo.vespa.config.server.session.SessionContext; import com.yahoo.vespa.config.server.session.SessionFactory; -import com.yahoo.vespa.config.server.session.SessionPreparer; -import com.yahoo.vespa.config.server.session.SessionTest; import com.yahoo.vespa.flags.InMemoryFlagSource; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Collection; import java.util.List; @@ -80,16 +78,13 @@ public class SessionHandlerTest { public static String getRenderedString(HttpResponse response) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); - return baos.toString("UTF-8"); + return baos.toString(StandardCharsets.UTF_8); } public static class MockSession extends LocalSession { - private final InMemoryFlagSource flagSource; public boolean doVerboseLogging = false; public Session.Status status; - private final SessionPreparer preparer; - private final ApplicationPackage app; private ConfigChangeActions actions = new ConfigChangeActions(); private long createTime = System.currentTimeMillis() / 1000; private ApplicationId applicationId; @@ -99,10 +94,7 @@ public class SessionHandlerTest { } private MockSession(long id, ApplicationPackage app, InMemoryFlagSource flagSource) { - super(TenantName.defaultName(), id, null, new SessionContext(null, new MockSessionZKClient(MockApplicationPackage.createEmpty()), null, null, new HostRegistry<>(), flagSource)); - this.app = app; - this.preparer = new SessionTest.MockSessionPreparer(); - this.flagSource = flagSource; + super(TenantName.defaultName(), id, null, new SessionContext(app, new MockSessionZKClient(app), null, null, new HostRegistry<>(), flagSource)); } public MockSession(long sessionId, ApplicationPackage applicationPackage, long createTime) { @@ -140,31 +132,17 @@ public class SessionHandlerTest { @Override public Transaction createDeactivateTransaction() { - return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> { - status = Status.DEACTIVATE; - }); + return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> status = Status.DEACTIVATE); } @Override public Transaction createActivateTransaction() { - return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> { - status = Status.ACTIVATE; - }); + return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> status = Status.ACTIVATE); } @Override public ApplicationFile getApplicationFile(Path relativePath, Mode mode) { - if (mode == Mode.WRITE) { - status = Status.NEW; - } - if (preparer == null) { - return null; - } - ApplicationPackage pkg = app; - if (pkg == null) { - return null; - } - return pkg.getFile(relativePath); + return this.applicationPackage.getFile(relativePath); } @Override @@ -205,8 +183,7 @@ public class SessionHandlerTest { public File applicationPackage; @Override - public LocalSession createSession(File applicationDirectory, ApplicationId applicationId, - TimeoutBudget timeoutBudget) { + public LocalSession createSession(File applicationDirectory, ApplicationId applicationId, TimeoutBudget timeoutBudget) { createCalled = true; if (doThrow) { throw new RuntimeException("foo"); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java index c6a8e1f2f9d..b7f55aa0670 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java @@ -53,17 +53,17 @@ public class ApplicationContentHandlerTest extends ContentHandlerTestBase { tenantRepository.addTenant(TenantBuilder.create(componentRegistry, tenantName1)); tenantRepository.addTenant(TenantBuilder.create(componentRegistry, tenantName2)); - session2 = new MockSession(2l, FilesApplicationPackage.fromFile(new File("src/test/apps/content"))); + session2 = new MockSession(2, FilesApplicationPackage.fromFile(new File("src/test/apps/content"))); Tenant tenant1 = tenantRepository.getTenant(tenantName1); tenant1.getLocalSessionRepo().addSession(session2); tenant1.getApplicationRepo().createApplication(idTenant1); - tenant1.getApplicationRepo().createPutTransaction(idTenant1, 2l).commit(); + tenant1.getApplicationRepo().createPutTransaction(idTenant1, 2).commit(); - MockSession session3 = new MockSession(3l, FilesApplicationPackage.fromFile(new File("src/test/apps/content2"))); + MockSession session3 = new MockSession(3, FilesApplicationPackage.fromFile(new File("src/test/apps/content2"))); Tenant tenant2 = tenantRepository.getTenant(tenantName2); tenant2.getLocalSessionRepo().addSession(session3); tenant2.getApplicationRepo().createApplication(idTenant2); - tenant2.getApplicationRepo().createPutTransaction(idTenant2, 3l).commit(); + tenant2.getApplicationRepo().createPutTransaction(idTenant2, 3).commit(); handler = new ApplicationHandler(ApplicationHandler.testOnlyContext(), Zone.defaultZone(), diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java index bc583c64206..fb09aa99039 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java @@ -44,9 +44,9 @@ public class HttpGetConfigHandlerTest { @Before public void setUp() { mockRequestHandler = new MockRequestHandler(); - mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{ + mockRequestHandler.setAllConfigs(new HashSet<>() {{ add(new ConfigKey<>("bar", "myid", "foo")); - }} ); + }} ); TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().build(); TenantRepository tenantRepository = new TenantRepository(componentRegistry, false); tenantRepository.addTenant(TenantBuilder.create(componentRegistry, tenant).withRequestHandler(mockRequestHandler)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java index 750ad1c9fc0..7789c5d88db 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java @@ -41,9 +41,9 @@ public class HttpListConfigsHandlerTest { @Before public void setUp() { mockRequestHandler = new MockRequestHandler(); - mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{ + mockRequestHandler.setAllConfigs(new HashSet<>() {{ add(new ConfigKey<>("bar", "conf/id", "foo")); - }} ); + }} ); TenantName tenantName = TenantName.from("mytenant"); TenantRepository tenantRepository = new TenantRepository(componentRegistry, false); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenantName) @@ -73,7 +73,7 @@ public class HttpListConfigsHandlerTest { @Test public void require_that_named_handler_can_be_created() throws IOException { HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/foo.bar/conf/id/", GET); - req.getJDiscRequest().parameters().put("http.path", Arrays.asList("foo.bar")); + req.getJDiscRequest().parameters().put("http.path", List.of("foo.bar")); HttpResponse response = namedHandler.handle(req); assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}")); } @@ -81,14 +81,14 @@ public class HttpListConfigsHandlerTest { @Test public void require_that_named_handler_can_be_created_from_full_appid() throws IOException { HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/environment/prod/region/myregion/instance/myinstance/foo.bar/conf/id/", GET); - req.getJDiscRequest().parameters().put("http.path", Arrays.asList("foo.bar")); + req.getJDiscRequest().parameters().put("http.path", List.of("foo.bar")); HttpResponse response = namedHandler.handle(req); assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}")); } @Test public void require_child_listings_correct() { - Set<ConfigKey<?>> keys = new LinkedHashSet<ConfigKey<?>>() {{ + Set<ConfigKey<?>> keys = new LinkedHashSet<>() {{ add(new ConfigKey<>("name1", "id/1", "ns1")); add(new ConfigKey<>("name1", "id/1", "ns1")); add(new ConfigKey<>("name1", "id/2", "ns1")); @@ -104,7 +104,7 @@ public class HttpListConfigsHandlerTest { @Test public void require_url_building_and_mimetype_correct() { - ListConfigsResponse resp = new ListConfigsResponse(new HashSet<ConfigKey<?>>(), null, "http://foo.com/config/v2/tenant/mytenant/application/mya/", true); + ListConfigsResponse resp = new ListConfigsResponse(new HashSet<>(), null, "http://foo.com/config/v2/tenant/mytenant/application/mya/", true); assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), true), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig/my/id"); assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), false), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig/my/id/"); assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "", "mynamespace"), false), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig"); @@ -128,7 +128,7 @@ public class HttpListConfigsHandlerTest { @Test public void require_correct_error_response_on_no_model() throws IOException { - mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>()); + mockRequestHandler.setAllConfigs(new HashSet<>()); HttpResponse response = namedHandler.handle(HttpRequest.createTestRequest("http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication/foo.bar/myid/", GET)); HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, @@ -137,7 +137,7 @@ public class HttpListConfigsHandlerTest { @Test public void require_correct_configid_parent() { - assertEquals(ListConfigsResponse.parentConfigId(null), null); + assertNull(ListConfigsResponse.parentConfigId(null)); assertEquals(ListConfigsResponse.parentConfigId("foo"), ""); assertEquals(ListConfigsResponse.parentConfigId(""), ""); assertEquals(ListConfigsResponse.parentConfigId("/"), ""); 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 d94194e58d9..52ecc5e2bae 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 @@ -21,7 +21,6 @@ import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.slime.JsonFormat; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.MockReloadHandler; -import com.yahoo.vespa.config.server.SuperModelGenerationCounter; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.OrchestratorMock; import com.yahoo.vespa.config.server.application.TenantApplications; @@ -37,7 +36,6 @@ import com.yahoo.vespa.config.server.session.LocalSession; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.session.MockSessionZKClient; import com.yahoo.vespa.config.server.session.RemoteSession; -import com.yahoo.vespa.config.server.session.RemoteSessionRepo; import com.yahoo.vespa.config.server.session.Session; import com.yahoo.vespa.config.server.session.SessionContext; import com.yahoo.vespa.config.server.session.SessionTest; @@ -58,7 +56,6 @@ import org.junit.rules.TemporaryFolder; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.time.Clock; import java.util.Collections; import java.util.Optional; @@ -81,9 +78,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { private static final String activatedMessage = " for tenant '" + tenantName + "' activated."; private final InMemoryFlagSource flagSource = new InMemoryFlagSource(); - private final Clock clock = Clock.systemUTC(); private Curator curator; - private RemoteSessionRepo remoteSessionRepo; private LocalSessionRepo localRepo; private TenantApplications applicationRepo; private MockProvisioner hostProvisioner; @@ -97,7 +92,6 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Before public void setup() { - remoteSessionRepo = new RemoteSessionRepo(tenantName); curator = new MockCurator(); modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); componentRegistry = new TestComponentRegistry.Builder() @@ -106,13 +100,12 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { .build(); tenantRepository = new TenantRepository(componentRegistry, false); applicationRepo = TenantApplications.create(componentRegistry, new MockReloadHandler(), tenantName); - localRepo = new LocalSessionRepo(clock, curator); + localRepo = new LocalSessionRepo(tenantName, componentRegistry); pathPrefix = "/application/v2/tenant/" + tenantName + "/session/"; hostProvisioner = new MockProvisioner(); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenantName) .withSessionFactory(new MockSessionFactory()) .withLocalSessionRepo(localRepo) - .withRemoteSessionRepo(remoteSessionRepo) .withApplicationRepo(applicationRepo); tenantRepository.addTenant(tenantBuilder); handler = createHandler(); @@ -120,15 +113,15 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Test public void testThatPreviousSessionIsDeactivated() throws Exception { - RemoteSession firstSession = activateAndAssertOK(90l, 0l); - activateAndAssertOK(91l, 90l); + RemoteSession firstSession = activateAndAssertOK(90, 0); + activateAndAssertOK(91, 90); assertThat(firstSession.getStatus(), Is.is(Session.Status.DEACTIVATE)); } @Test public void testForceActivationWithActivationInBetween() throws Exception { - activateAndAssertOK(90l, 0l); - activateAndAssertOK(92l, 89l, "?force=true"); + activateAndAssertOK(90, 0); + activateAndAssertOK(92, 89, "?force=true"); } @Test @@ -140,9 +133,9 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Test public void testActivationWithBarrierTimeout() throws Exception { // Needed so we can test that previous active session is still active after a failed activation - activateAndAssertOK(90l, 0l); + activateAndAssertOK(90, 0); ((MockCurator) curator).timeoutBarrierOnEnter(true); - ActivateRequest activateRequest = new ActivateRequest(91l, 90l, "", Clock.systemUTC()).invoke(); + ActivateRequest activateRequest = new ActivateRequest(91, 90, "").invoke(); HttpResponse actResponse = activateRequest.getActResponse(); assertThat(actResponse.getStatus(), Is.is(INTERNAL_SERVER_ERROR)); } @@ -155,7 +148,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { activateAndAssertOK(sessionId, 1); sessionId++; - ActivateRequest activateRequest = new ActivateRequest(sessionId, 1, "", clock).invoke(); + ActivateRequest activateRequest = new ActivateRequest(sessionId, 1, "").invoke(); HttpResponse actResponse = activateRequest.getActResponse(); String message = getRenderedString(actResponse); assertThat(message, actResponse.getStatus(), Is.is(CONFLICT)); @@ -166,7 +159,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Test public void testAlreadyActivatedSession() throws Exception { activateAndAssertOK(1, 0); - HttpResponse response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, 1l)); + HttpResponse response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, 1L)); String message = getRenderedString(response); assertThat(message, response.getStatus(), Is.is(BAD_REQUEST)); assertThat(message, containsString("Session 1 is already active")); @@ -179,8 +172,8 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Test public void testActivationWithActivationInBetween() throws Exception { - activateAndAssertOK(90l, 0l); - activateAndAssertError(92l, 89l, clock, + activateAndAssertOK(90, 0); + activateAndAssertError(92, 89, Response.Status.CONFLICT, HttpErrorResponse.errorCodes.ACTIVATION_CONFLICT, "tenant:" + tenantName + " app:default:default Cannot activate session 92 because the currently active session (90) has changed since session 92 was created (was 89 at creation time)"); } @@ -188,9 +181,9 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { @Test public void testActivationOfUnpreparedSession() throws Exception { // Needed so we can test that previous active session is still active after a failed activation - RemoteSession firstSession = activateAndAssertOK(90l, 0l); + RemoteSession firstSession = activateAndAssertOK(90, 0); long sessionId = 91L; - ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "", clock).invoke(); + ActivateRequest activateRequest = new ActivateRequest(sessionId, 0, Session.Status.NEW, "").invoke(); HttpResponse actResponse = activateRequest.getActResponse(); RemoteSession session = activateRequest.getSession(); assertThat(actResponse.getStatus(), is(Response.Status.BAD_REQUEST)); @@ -211,7 +204,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { public void require_that_handler_gives_error_when_provisioner_activated_fails() throws Exception { hostProvisioner = new FailingMockProvisioner(); hostProvisioner.activated = false; - activateAndAssertError(1, 0, clock, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Cannot activate application"); + activateAndAssertError(1, 0, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Cannot activate application"); assertFalse(hostProvisioner.activated); } @@ -221,9 +214,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); zkC.write(Collections.singletonMap(modelFactory.version(), new MockFileRegistry())); zkC.write(AllocatedHosts.withHosts(Collections.emptySet())); - RemoteSession session = new RemoteSession(tenantName, sessionId, componentRegistry, zkClient); - remoteSessionRepo.addSession(session); - return session; + return new RemoteSession(tenantName, sessionId, componentRegistry, zkClient); } private void addLocalSession(long sessionId, DeployData deployData, SessionZooKeeperClient zkc) throws IOException { @@ -237,7 +228,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { } private ActivateRequest activateAndAssertOKPut(long sessionId, long previousSessionId, String subPath) throws Exception { - ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, subPath, clock); + ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, subPath); activateRequest.invoke(); HttpResponse actResponse = activateRequest.getActResponse(); String message = getRenderedString(actResponse); @@ -248,9 +239,9 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { return activateRequest; } - private void activateAndAssertErrorPut(long sessionId, long previousSessionId, Clock clock, + private void activateAndAssertErrorPut(long sessionId, long previousSessionId, int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { - ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, "", clock); + ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, ""); activateRequest.invoke(); HttpResponse actResponse = activateRequest.getActResponse(); RemoteSession session = activateRequest.getSession(); @@ -277,24 +268,22 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { private DeployData deployData; private ApplicationMetaData metaData; private String subPath; - private Clock clock; - ActivateRequest(long sessionId, long previousSessionId, String subPath, Clock clock) { - this(sessionId, previousSessionId, Session.Status.PREPARE, subPath, clock); + ActivateRequest(long sessionId, long previousSessionId, String subPath) { + this(sessionId, previousSessionId, Session.Status.PREPARE, subPath); } - ActivateRequest(long sessionId, long previousSessionId, Session.Status initialStatus, String subPath, Clock clock) { + ActivateRequest(long sessionId, long previousSessionId, Session.Status initialStatus, String subPath) { this.sessionId = sessionId; this.initialStatus = initialStatus; this.deployData = new DeployData("foo", "bar", appName, - 0l, + 0L, false, sessionId, previousSessionId); this.subPath = subPath; - this.clock = clock; } public RemoteSession getSession() { @@ -354,9 +343,9 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { assertThat(hostProvisioner.lastHosts.size(), is(1)); } - private void activateAndAssertError(long sessionId, long previousSessionId, Clock clock, int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { + private void activateAndAssertError(long sessionId, long previousSessionId, int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { hostProvisioner.activated = false; - activateAndAssertErrorPut(sessionId, previousSessionId, clock, statusCode, errorCode, expectedError); + activateAndAssertErrorPut(sessionId, previousSessionId, statusCode, errorCode, expectedError); assertFalse(hostProvisioner.activated); } @@ -370,7 +359,7 @@ public class SessionActiveHandlerTest extends SessionHandlerTest { new ApplicationRepository(tenantRepository, hostProvisioner, new OrchestratorMock(), - clock), + componentRegistry.getClock()), tenantRepository, Zone.defaultZone()); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java index 42b3fadc0de..d4dd17ca280 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java @@ -24,7 +24,6 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.time.Clock; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNotNull; @@ -37,7 +36,6 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { private static final TenantName tenant = TenantName.from("contenttest"); private final TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().build(); - private final Clock clock = componentRegistry.getClock(); private TenantRepository tenantRepository; private SessionContentHandler handler = null; @@ -59,10 +57,11 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { assertMkdir("/bar/brask/"); assertMkdir("/bar/brask/bram/"); assertMkdir("/brask/og/bram/"); - }// TODO: Enable when we have a predictable way of checking request body existence. + } @Test @Ignore + // TODO: Enable when we have a predictable way of checking request body existence. public void require_that_mkdir_with_body_is_illegal(){ HttpResponse response = put("/foobio/", "foo"); assertNotNull(response); @@ -70,15 +69,15 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { } @Test - public void require_that_nonexistant_session_returns_not_found() { - HttpResponse response = doRequest(HttpRequest.Method.GET, "/test.txt", 2l); + public void require_that_nonexistent_session_returns_not_found() { + HttpResponse response = doRequest(HttpRequest.Method.GET, "/test.txt", 2); assertNotNull(response); assertThat(response.getStatus(), is(Response.Status.NOT_FOUND)); } protected HttpResponse put(String path, String content) { ByteArrayInputStream data = new ByteArrayInputStream(Utf8.toBytes(content)); - return doRequest(HttpRequest.Method.PUT, path, data); + return doRequest(HttpRequest.Method.PUT, path, 1, data); } @Test @@ -95,7 +94,7 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { } @Test - public void require_that_nonexistant_file_returs_not_found_when_deleted() throws IOException { + public void require_that_nonexistent_file_returns_not_found_when_deleted() throws IOException { assertDeleteFile(Response.Status.NOT_FOUND, "/test2.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session 1 does not contain a file 'test2.txt'\"}"); } @@ -152,17 +151,13 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { } protected HttpResponse doRequest(HttpRequest.Method method, String path) { - return doRequest(method, path, 1l); + return doRequest(method, path, 1); } private HttpResponse doRequest(HttpRequest.Method method, String path, long sessionId) { return handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, method, Cmd.CONTENT, sessionId, path)); } - private HttpResponse doRequest(HttpRequest.Method method, String path, InputStream data) { - return doRequest(method, path, 1l, data); - } - private HttpResponse doRequest(HttpRequest.Method method, String path, long sessionId, InputStream data) { return handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, method, Cmd.CONTENT, sessionId, path, data)); } @@ -173,7 +168,7 @@ public class SessionContentHandlerTest extends ContentHandlerTestBase { new ApplicationRepository(tenantRepository, new SessionHandlerTest.MockProvisioner(), new OrchestratorMock(), - clock), + componentRegistry.getClock()), tenantRepository); } } 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 ac4b4ea9005..6d2c2d35ab9 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 @@ -18,8 +18,6 @@ import com.yahoo.vespa.config.server.http.SessionHandlerTest; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.tenant.TenantBuilder; import com.yahoo.vespa.config.server.tenant.TenantRepository; -import com.yahoo.vespa.curator.Curator; -import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -28,7 +26,6 @@ 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; @@ -53,7 +50,6 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { private static final HashMap<String, String> postHeaders = new HashMap<>(); private final TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().build(); - private final Clock clock = componentRegistry.getClock(); private String pathPrefix = "/application/v2/session/"; private String createdMessage = " created.\""; @@ -71,9 +67,8 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { @Before public void setupRepo() { - Curator curator = new MockCurator(); applicationRepo = TenantApplications.create(componentRegistry, new MockReloadHandler(), tenant); - localSessionRepo = new LocalSessionRepo(Clock.systemUTC(), curator); + localSessionRepo = new LocalSessionRepo(tenant, componentRegistry); tenantRepository = new TenantRepository(componentRegistry, false); sessionFactory = new MockSessionFactory(); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenant) @@ -182,13 +177,12 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { public void require_that_session_is_stored_in_repo() throws IOException { File outFile = CompressedApplicationInputStreamTest.createTarFile(); createHandler().handle(post(outFile)); - assertNotNull(localSessionRepo.getSession(0l)); + assertNotNull(localSessionRepo.getSession(0)); } - @Test public void require_that_application_urls_can_be_given_as_from_parameter() throws Exception { - localSessionRepo.addSession(new SessionHandlerTest.MockSession(2l, FilesApplicationPackage.fromFile(testApp))); + localSessionRepo.addSession(new SessionHandlerTest.MockSession(2, FilesApplicationPackage.fromFile(testApp))); ApplicationId fooId = new ApplicationId.Builder() .tenant(tenant) .applicationName("foo") @@ -197,7 +191,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { applicationRepo.createApplication(fooId); applicationRepo.createPutTransaction(fooId, 2).commit(); assertFromParameter("3", "http://myhost:40555/application/v2/tenant/" + tenant + "/application/foo/environment/test/region/baz/instance/quux"); - localSessionRepo.addSession(new SessionHandlerTest.MockSession(5l, FilesApplicationPackage.fromFile(testApp))); + localSessionRepo.addSession(new SessionHandlerTest.MockSession(5, FilesApplicationPackage.fromFile(testApp))); ApplicationId bioId = new ApplicationId.Builder() .tenant(tenant) .applicationName("foobio") @@ -224,7 +218,7 @@ public class SessionCreateHandlerTest extends SessionHandlerTest { new ApplicationRepository(tenantRepository, new SessionHandlerTest.MockProvisioner(), new OrchestratorMock(), - clock), + componentRegistry.getClock()), tenantRepository, componentRegistry.getConfigserverConfig()); 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 b6c3de8a1b1..e201ce107bd 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 @@ -46,9 +46,10 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import static com.yahoo.jdisc.Response.Status.*; import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; +import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; +import static com.yahoo.jdisc.Response.Status.OK; import static com.yahoo.vespa.config.server.http.HandlerTest.assertHttpStatusCodeErrorCodeAndMessage; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; @@ -63,30 +64,25 @@ import static org.junit.Assert.assertThat; public class SessionPrepareHandlerTest extends SessionHandlerTest { private static final TenantName tenant = TenantName.from("test"); - private final TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().build(); + private Curator curator = new MockCurator(); + private final TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().curator(curator).build(); private final Clock clock = componentRegistry.getClock(); - - private Curator curator; private LocalSessionRepo localRepo; private String preparedMessage = " prepared.\"}"; private String tenantMessage = ""; - private RemoteSessionRepo remoteSessionRepo; private TenantRepository tenantRepository; @Before public void setupRepo() { - curator = new MockCurator(); - localRepo = new LocalSessionRepo(clock, curator); + localRepo = new LocalSessionRepo(tenant, componentRegistry); pathPrefix = "/application/v2/tenant/" + tenant + "/session/"; preparedMessage = " for tenant '" + tenant + "' prepared.\""; tenantMessage = ",\"tenant\":\"" + tenant + "\""; tenantRepository = new TenantRepository(componentRegistry, false); - remoteSessionRepo = new RemoteSessionRepo(tenant); TenantBuilder tenantBuilder = TenantBuilder.create(componentRegistry, tenant) .withSessionFactory(new MockSessionFactory()) .withLocalSessionRepo(localRepo) - .withRemoteSessionRepo(remoteSessionRepo) .withApplicationRepo(TenantApplications.create(componentRegistry, new MockReloadHandler(), tenant)); tenantRepository.addTenant(tenantBuilder); } @@ -151,21 +147,8 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { assertThat(SessionHandlerTest.getRenderedString(response), containsString("debuglog")); } - /** - * A mock remote session repo based on contents of local repo. Only works when there is just one session in local repo - */ - // TODO: Fix this mess - private SessionZooKeeperClient fromLocalSessionRepo(LocalSessionRepo localRepo) { - SessionZooKeeperClient zooKeeperClient = null; - for (LocalSession ls : localRepo.listSessions()) { - zooKeeperClient = new MockSessionZKClient(curator, tenant, ls.getSessionId()); - if (ls.getStatus()!=null) zooKeeperClient.writeStatus(ls.getStatus()); - RemoteSession remSess = new RemoteSession(tenant, ls.getSessionId(), - new TestComponentRegistry.Builder().curator(curator).build(), - zooKeeperClient); - remoteSessionRepo.addSession(remSess); - } - return zooKeeperClient; + private SessionZooKeeperClient createSessionZooKeeperClient(LocalSession session) { + return new MockSessionZKClient(curator, tenant, session.getSessionId()); } @Test @@ -175,7 +158,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { SessionHandler sessHandler = createHandler(); sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); session.setStatus(Session.Status.PREPARE); - SessionZooKeeperClient zooKeeperClient = fromLocalSessionRepo(localRepo); + SessionZooKeeperClient zooKeeperClient = createSessionZooKeeperClient(session); zooKeeperClient.writeStatus(Session.Status.PREPARE); HttpResponse getResponse = sessHandler.handle( SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 1L)); @@ -189,7 +172,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { localRepo.addSession(session); SessionHandler sessHandler = createHandler(); session.setStatus(Session.Status.NEW); - SessionZooKeeperClient zooKeeperClient = fromLocalSessionRepo(localRepo); + SessionZooKeeperClient zooKeeperClient = createSessionZooKeeperClient(session); zooKeeperClient.writeStatus(Session.Status.NEW); HttpResponse getResponse = sessHandler.handle( SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 1L)); @@ -244,12 +227,11 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { @Test public void require_that_preparing_with_multiple_tenants_work() throws Exception { - // Need different repo for 'test2' tenant - LocalSessionRepo localRepoDefault = new LocalSessionRepo(clock, curator); final TenantName defaultTenant = TenantName.from("test2"); + // Need different repo for 'test2' tenant + LocalSessionRepo localRepoDefault = new LocalSessionRepo(defaultTenant, componentRegistry); TenantBuilder defaultTenantBuilder = TenantBuilder.create(componentRegistry, defaultTenant) .withLocalSessionRepo(localRepoDefault) - .withRemoteSessionRepo(new RemoteSessionRepo(defaultTenant)) .withSessionFactory(new MockSessionFactory()); tenantRepository.addTenant(defaultTenantBuilder); final SessionHandler handler = createHandler(); @@ -326,7 +308,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } @Test - public void test_out_of_capacity_response() throws InterruptedException, IOException { + public void test_out_of_capacity_response() throws IOException { String message = "Internal error"; SessionThrowingException session = new SessionThrowingException(new OutOfCapacityException(message)); localRepo.addSession(session); @@ -339,7 +321,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } @Test - public void test_that_nullpointerexception_gives_internal_server_error() throws InterruptedException, IOException { + public void test_that_nullpointerexception_gives_internal_server_error() throws IOException { String message = "No nodes available"; SessionThrowingException session = new SessionThrowingException(new NullPointerException(message)); localRepo.addSession(session); @@ -352,7 +334,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } @Test - public void test_application_lock_failure() throws InterruptedException, IOException { + public void test_application_lock_failure() throws IOException { String message = "Timed out after waiting PT1M to acquire lock '/provision/v1/locks/foo/bar/default'"; SessionThrowingException session = new SessionThrowingException(new ApplicationLockException(new UncheckedTimeoutException(message))); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java index 6effa3359b1..9d843a1f2c6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.*; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.time.Clock; import com.yahoo.config.provision.ApplicationId; @@ -139,7 +140,7 @@ public class TenantHandlerTest { private void assertResponseEquals(SessionResponse response, String payload) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); - assertEquals(baos.toString("UTF-8"), payload); + assertEquals(baos.toString(StandardCharsets.UTF_8), payload); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/maintenance/MaintainerTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/maintenance/MaintainerTester.java index 659baf5a184..c2489a1d3e9 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/maintenance/MaintainerTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/maintenance/MaintainerTester.java @@ -10,8 +10,6 @@ import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; -import java.time.Clock; - class MaintainerTester { private final Curator curator; @@ -25,7 +23,7 @@ class MaintainerTester { applicationRepository = new ApplicationRepository(tenantRepository, new SessionHandlerTest.MockProvisioner(), new OrchestratorMock(), - Clock.systemUTC()); + componentRegistry.getClock()); } Curator curator() { return curator; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/metrics/MetricsRetrieverTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/metrics/MetricsRetrieverTest.java new file mode 100644 index 00000000000..1b878a432c9 --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/metrics/MetricsRetrieverTest.java @@ -0,0 +1,99 @@ +package com.yahoo.vespa.config.server.metrics; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import junit.framework.AssertionFailedError; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.*; + + +/** + * @author olaa + */ +public class MetricsRetrieverTest { + + @Rule + public final WireMockRule wireMock = new WireMockRule(options().port(8080), true); + + @Test + public void testMetricAggregation() throws IOException { + var metricsRetriever = new MetricsRetriever(); + + var clusters = List.of( + new ClusterInfo("cluster1", ClusterInfo.ClusterType.content, List.of(URI.create("http://localhost:8080/1"), URI.create("http://localhost:8080/2"))), + new ClusterInfo("cluster2", ClusterInfo.ClusterType.container, List.of(URI.create("http://localhost:8080/3"))) + ); + + stubFor(get(urlEqualTo("/1")) + .willReturn(aResponse() + .withStatus(200) + .withBody(contentMetrics()))); + + stubFor(get(urlEqualTo("/2")) + .willReturn(aResponse() + .withStatus(200) + .withBody(contentMetrics()))); + + stubFor(get(urlEqualTo("/3")) + .willReturn(aResponse() + .withStatus(200) + .withBody(containerMetrics()))); + + compareAggregators( + new MetricsAggregator().addDocumentCount(6000.0), + metricsRetriever.requestMetricsForCluster(clusters.get(0)) + ); + + compareAggregators( + new MetricsAggregator() + .addContainerLatency(3000, 43) + .addContainerLatency(2000, 0) + .addQrLatency(3000, 43) + .addFeedLatency(3000, 43), + metricsRetriever.requestMetricsForCluster(clusters.get(1)) + ); + + wireMock.stop(); + } + + private String containerMetrics() throws IOException { + return Files.readString(Path.of("src/test/resources/metrics/container_metrics")); + } + + private String contentMetrics() throws IOException { + return Files.readString(Path.of("src/test/resources/metrics/content_metrics")); + } + + // Same tolerance value as used internally in MetricsAggregator.isZero + private static final double metricsTolerance = 0.001; + + private void compareAggregators(MetricsAggregator expected, MetricsAggregator actual) { + BiConsumer<Double, Double> assertDoubles = (a, b) -> assertEquals(a.doubleValue(), b.doubleValue(), metricsTolerance); + + compareOptionals(expected.aggregateDocumentCount(), actual.aggregateDocumentCount(), assertDoubles); + compareOptionals(expected.aggregateQueryRate(), actual.aggregateQueryRate(), assertDoubles); + compareOptionals(expected.aggregateFeedRate(), actual.aggregateFeedRate(), assertDoubles); + compareOptionals(expected.aggregateQueryLatency(), actual.aggregateQueryLatency(), assertDoubles); + compareOptionals(expected.aggregateFeedLatency(), actual.aggregateFeedLatency(), assertDoubles); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private static <T> void compareOptionals(Optional<T> a, Optional<T> b, BiConsumer<T, T> comparer) { + if (a.isPresent() != b.isPresent()) throw new AssertionFailedError("Both optionals are not present: " + a + ", " + b); + a.ifPresent(x -> b.ifPresent(y -> comparer.accept(x, y))); + } +}
\ No newline at end of file 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 34a4074c7cf..9b678b9b37f 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 @@ -28,7 +28,6 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -212,7 +211,7 @@ public class LbServicesProducerTest { private ApplicationInfo createApplication(ApplicationId appId, DeployState.Builder deploystateBuilder) throws IOException, SAXException { return new ApplicationInfo( appId, - 3l, + 3, createVespaModel(createApplicationPackage( appId.tenant() + "." + appId.application() + ".yahoo.com", appId.tenant().value() + "." + appId.application().value() + "2.yahoo.com"), deploystateBuilder)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java index 7ce2c39c6a4..5a34dd4c912 100755 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java @@ -66,7 +66,7 @@ public class RoutingProducerTest { private ApplicationInfo createApplication(ApplicationId appId, DeployState.Builder deploystateBuilder) throws IOException, SAXException { return new ApplicationInfo( appId, - 3l, + 3L, createVespaModel( createApplicationPackage( appId.tenant() + "." + appId.application() + ".yahoo.com", diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdaterTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdaterTest.java index 28c6dddd5a7..ed089109759 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdaterTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/monitoring/ZKMetricUpdaterTest.java @@ -62,8 +62,7 @@ public class ZKMetricUpdaterTest { private ZKMetricUpdater buildUpdater() { ZookeeperServerConfig zkServerConfig = new ZookeeperServerConfig( new ZookeeperServerConfig.Builder().clientPort(serverPort).myid(12345)); - ZKMetricUpdater updater = new ZKMetricUpdater(zkServerConfig, 0, -1); - return updater; + return new ZKMetricUpdater(zkServerConfig, 0, -1); } private void setupTcpServer(Supplier<String> reportProvider) throws IOException { 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 819078d296b..87459228d0d 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 @@ -17,11 +17,13 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.IOException; +import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.Optional; import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -52,7 +54,7 @@ public class DelayedConfigResponseTest { assertThat(responses.size(), is(2)); assertTrue(req.isDelayedResponse()); List<DelayedConfigResponses.DelayedConfigResponse> it = responses.allDelayedResponses(); - assertTrue(!it.isEmpty()); + assertFalse(it.isEmpty()); } @Test @@ -74,7 +76,7 @@ public class DelayedConfigResponseTest { assertThat(responses.toString(), is("DelayedConfigResponses. Average Size=0")); JRTServerConfigRequest req = createRequest("foo", "md5", "myid", "mymd5", 3, 100, "bar"); responses.delayResponse(req, GetConfigContext.testContext(ApplicationId.defaultId())); - rpc.waitUntilSet(5000); + rpc.waitUntilSet(Duration.ofSeconds(5)); assertThat(rpc.latestRequest, is(req)); } 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 5fa51e1c404..07f6e9cf222 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 @@ -15,6 +15,8 @@ import com.yahoo.vespa.config.server.rpc.security.NoopRpcAuthorizer; import com.yahoo.vespa.config.server.tenant.MockTenantProvider; import java.io.File; +import java.time.Duration; +import java.time.Instant; import java.util.Optional; import java.util.concurrent.CompletionService; @@ -67,10 +69,9 @@ public class MockRpc extends RpcServer { return new ConfigserverConfig(b); } - public boolean waitUntilSet(int timeout) { - long start = System.currentTimeMillis(); - long end = start + timeout; - while (start < end) { + boolean waitUntilSet(Duration timeout) { + Instant end = Instant.now().plus(timeout); + while (Instant.now().isBefore(end)) { if (latestRequest != null) return true; try { 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 d3f364e30ac..9232109a89b 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 @@ -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.session; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.test.ManualClock; import com.yahoo.config.provision.TenantName; @@ -8,7 +9,6 @@ import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.TenantApplications; -import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs; import com.yahoo.io.IOUtils; import com.yahoo.vespa.config.server.host.HostRegistry; import com.yahoo.vespa.config.server.http.SessionHandlerTest; @@ -20,6 +20,8 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; @@ -46,61 +48,68 @@ public class LocalSessionRepoTest { } private void setupSessions(TenantName tenantName, boolean createInitialSessions) throws Exception { - TenantFileSystemDirs tenantFileSystemDirs = new TenantFileSystemDirs(temporaryFolder.newFolder(), tenantName); + File configserverDbDir = temporaryFolder.newFolder().getAbsoluteFile(); if (createInitialSessions) { - IOUtils.copyDirectory(testApp, new File(tenantFileSystemDirs.sessionsPath(), "1")); - IOUtils.copyDirectory(testApp, new File(tenantFileSystemDirs.sessionsPath(), "2")); - IOUtils.copyDirectory(testApp, new File(tenantFileSystemDirs.sessionsPath(), "3")); + Path sessionsPath = Paths.get(configserverDbDir.getAbsolutePath(), "tenants", tenantName.value(), "sessions"); + IOUtils.copyDirectory(testApp, sessionsPath.resolve("1").toFile()); + IOUtils.copyDirectory(testApp, sessionsPath.resolve("2").toFile()); + IOUtils.copyDirectory(testApp, sessionsPath.resolve("3").toFile()); } clock = new ManualClock(Instant.ofEpochSecond(1)); - GlobalComponentRegistry globalComponentRegistry = new TestComponentRegistry.Builder().curator(new MockCurator()) - .clock(clock) - .build(); + GlobalComponentRegistry globalComponentRegistry = new TestComponentRegistry.Builder() + .curator(new MockCurator()) + .clock(clock) + .configServerConfig(new ConfigserverConfig.Builder() + .configServerDBDir(configserverDbDir.getAbsolutePath()) + .configDefinitionsDir(temporaryFolder.newFolder().getAbsolutePath()) + .sessionLifetime(5) + .build()) + .build(); LocalSessionLoader loader = new SessionFactoryImpl(globalComponentRegistry, TenantApplications.create(globalComponentRegistry, new MockReloadHandler(), tenantName), - tenantFileSystemDirs, new HostRegistry<>(), + new HostRegistry<>(), tenantName); - repo = new LocalSessionRepo(tenantName, globalComponentRegistry, tenantFileSystemDirs, loader); + repo = new LocalSessionRepo(tenantName, globalComponentRegistry, loader); } @Test public void require_that_sessions_can_be_loaded_from_disk() { - assertNotNull(repo.getSession(1l)); - assertNotNull(repo.getSession(2l)); - assertNotNull(repo.getSession(3l)); - assertNull(repo.getSession(4l)); + assertNotNull(repo.getSession(1L)); + assertNotNull(repo.getSession(2L)); + assertNotNull(repo.getSession(3L)); + assertNull(repo.getSession(4L)); } @Test public void require_that_old_sessions_are_purged() { clock.advance(Duration.ofSeconds(1)); - assertNotNull(repo.getSession(1l)); - assertNotNull(repo.getSession(2l)); - assertNotNull(repo.getSession(3l)); + assertNotNull(repo.getSession(1L)); + assertNotNull(repo.getSession(2L)); + assertNotNull(repo.getSession(3L)); clock.advance(Duration.ofSeconds(1)); - assertNotNull(repo.getSession(1l)); - assertNotNull(repo.getSession(2l)); - assertNotNull(repo.getSession(3l)); + assertNotNull(repo.getSession(1L)); + assertNotNull(repo.getSession(2L)); + assertNotNull(repo.getSession(3L)); clock.advance(Duration.ofSeconds(1)); - addSession(4l, 6); - assertNotNull(repo.getSession(1l)); - assertNotNull(repo.getSession(2l)); - assertNotNull(repo.getSession(3l)); - assertNotNull(repo.getSession(4l)); + addSession(4L, 6); + assertNotNull(repo.getSession(1L)); + assertNotNull(repo.getSession(2L)); + assertNotNull(repo.getSession(3L)); + assertNotNull(repo.getSession(4L)); clock.advance(Duration.ofSeconds(1)); - addSession(5l, 10); + addSession(5L, 10); repo.purgeOldSessions(); - assertNull(repo.getSession(1l)); - assertNull(repo.getSession(2l)); - assertNull(repo.getSession(3l)); + assertNull(repo.getSession(1L)); + assertNull(repo.getSession(2L)); + assertNull(repo.getSession(3L)); } @Test public void require_that_all_sessions_are_deleted() { - repo.deleteAllSessions(); - assertNull(repo.getSession(1l)); - assertNull(repo.getSession(2l)); - assertNull(repo.getSession(3l)); + repo.close(); + assertNull(repo.getSession(1L)); + assertNull(repo.getSession(2L)); + assertNull(repo.getSession(3L)); } private void addSession(long sessionId, long createTime) { @@ -110,10 +119,10 @@ public class LocalSessionRepoTest { @Test public void require_that_sessions_belong_to_a_tenant() { // tenant is "default" - assertNotNull(repo.getSession(1l)); - assertNotNull(repo.getSession(2l)); - assertNotNull(repo.getSession(3l)); - assertNull(repo.getSession(4l)); + assertNotNull(repo.getSession(1L)); + assertNotNull(repo.getSession(2L)); + assertNotNull(repo.getSession(3L)); + assertNull(repo.getSession(4L)); // tenant is "newTenant" try { @@ -121,12 +130,12 @@ public class LocalSessionRepoTest { } catch (Exception e) { fail(); } - assertNull(repo.getSession(1l)); + assertNull(repo.getSession(1L)); - repo.addSession(new SessionHandlerTest.MockSession(1l, FilesApplicationPackage.fromFile(testApp))); - repo.addSession(new SessionHandlerTest.MockSession(2l, FilesApplicationPackage.fromFile(testApp))); - assertNotNull(repo.getSession(1l)); - assertNotNull(repo.getSession(2l)); - assertNull(repo.getSession(3l)); + repo.addSession(new SessionHandlerTest.MockSession(1L, FilesApplicationPackage.fromFile(testApp))); + repo.addSession(new SessionHandlerTest.MockSession(2L, FilesApplicationPackage.fromFile(testApp))); + assertNotNull(repo.getSession(1L)); + assertNotNull(repo.getSession(2L)); + assertNull(repo.getSession(3L)); } } 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 96caff9b3a7..b357f712004 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 @@ -65,17 +65,17 @@ public class LocalSessionTest { @Test public void require_that_session_is_initialized() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 2); - assertThat(session.getSessionId(), is(2l)); + assertThat(session.getSessionId(), is(2L)); session = createSession(TenantName.defaultName(), Long.MAX_VALUE); assertThat(session.getSessionId(), is(Long.MAX_VALUE)); - assertThat(session.getActiveSessionAtCreate(), is(0l)); + assertThat(session.getActiveSessionAtCreate(), is(0L)); } @Test public void require_that_session_status_is_updated() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 3); assertThat(session.getStatus(), is(Session.Status.NEW)); - doPrepare(session, Instant.now()); + doPrepare(session); assertThat(session.getStatus(), is(Session.Status.PREPARE)); session.createActivateTransaction().commit(); assertThat(session.getStatus(), is(Session.Status.ACTIVATE)); @@ -84,7 +84,7 @@ public class LocalSessionTest { @Test public void require_that_marking_session_modified_changes_status_to_new() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 3); - doPrepare(session, Instant.now()); + doPrepare(session); assertThat(session.getStatus(), is(Session.Status.PREPARE)); session.getApplicationFile(Path.createRoot(), LocalSession.Mode.READ); assertThat(session.getStatus(), is(Session.Status.PREPARE)); @@ -97,7 +97,7 @@ public class LocalSessionTest { SessionTest.MockSessionPreparer preparer = new SessionTest.MockSessionPreparer(); LocalSession session = createSession(TenantName.defaultName(), 3, preparer); assertFalse(preparer.isPrepared); - doPrepare(session, Instant.now()); + doPrepare(session); assertTrue(preparer.isPrepared); assertThat(session.getStatus(), is(Session.Status.PREPARE)); } @@ -148,7 +148,7 @@ public class LocalSessionTest { NetworkPorts ports = new NetworkPorts(list); AllocatedHosts input = AllocatedHosts.withHosts(Collections.singleton( - new HostSpec("myhost", Collections.<String>emptyList(), + new HostSpec("myhost", Collections.emptyList(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(ports)))); @@ -156,7 +156,7 @@ public class LocalSessionTest { ApplicationId origId = new ApplicationId.Builder() .tenant("tenant") .applicationName("foo").instanceName("quux").build(); - doPrepare(session, new PrepareParams.Builder().applicationId(origId).build(), Instant.now()); + doPrepare(session, new PrepareParams.Builder().applicationId(origId).build()); AllocatedHosts info = session.getAllocatedHosts(); assertNotNull(info); assertThat(info.getHosts().size(), is(1)); @@ -169,7 +169,7 @@ public class LocalSessionTest { @Test public void require_that_application_metadata_is_correct() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 3); - doPrepare(session, new PrepareParams.Builder().build(), Instant.now()); + doPrepare(session, new PrepareParams.Builder().build()); assertThat(session.getMetaData().toString(), is("n/a, n/a, 0, 0, , 0")); } @@ -179,7 +179,7 @@ public class LocalSessionTest { } private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer) throws Exception { - return createSession(tenant, sessionId, preparer, Optional.<AllocatedHosts>empty()); + return createSession(tenant, sessionId, preparer, Optional.empty()); } private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer, Optional<AllocatedHosts> allocatedHosts) throws Exception { @@ -204,16 +204,16 @@ public class LocalSessionTest { flagSource)); } - private void doPrepare(LocalSession session, Instant now) { - doPrepare(session, new PrepareParams.Builder().build(), now); + private void doPrepare(LocalSession session) { + doPrepare(session, new PrepareParams.Builder().build()); } - private void doPrepare(LocalSession session, PrepareParams params, Instant now) { - session.prepare(getLogger(false), params, Optional.empty(), tenantPath, now); + private void doPrepare(LocalSession session, PrepareParams params) { + session.prepare(getLogger(), params, Optional.empty(), tenantPath, Instant.now()); } - DeployHandlerLogger getLogger(boolean verbose) { - return new DeployHandlerLogger(new Slime().get(), verbose, + private DeployHandlerLogger getLogger() { + return new DeployHandlerLogger(new Slime().get(), false, new ApplicationId.Builder().tenant("testtenant").applicationName("testapp").build()); } 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 e22185ae69b..b0e184398e5 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 @@ -5,9 +5,6 @@ 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.session.Session.Status; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; @@ -23,7 +20,6 @@ public class MockSessionZKClient extends SessionZooKeeperClient { private ApplicationPackage app; private Optional<AllocatedHosts> info = Optional.empty(); - private Status sessionStatus; public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId) { this(curator, tenantName, sessionId, (ApplicationPackage) null); @@ -34,7 +30,7 @@ public class MockSessionZKClient extends SessionZooKeeperClient { this.info = allocatedHosts; } - public MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, ApplicationPackage application) { + MockSessionZKClient(Curator curator, TenantName tenantName, long sessionId, ApplicationPackage application) { super(curator, TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); this.app = application; curator.create(TenantRepository.getSessionsPath(tenantName).append(String.valueOf(sessionId))); 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 83183a27666..9110671b494 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 @@ -49,8 +49,8 @@ public class RemoteSessionRepoTest { this.remoteSessionRepo = tenant.getRemoteSessionRepo(); curator.create(TenantRepository.getTenantPath(tenantName).append("/applications")); curator.create(TenantRepository.getSessionsPath(tenantName)); - createSession(1l, false); - createSession(2l, false); + createSession(1L, false); + createSession(2L, false); } private void createSession(long sessionId, boolean wait) { @@ -69,14 +69,14 @@ public class RemoteSessionRepoTest { @Test public void testInitialize() { - assertSessionExists(1l); - assertSessionExists(2l); + assertSessionExists(1L); + assertSessionExists(2L); } @Test public void testCreateSession() { - createSession(3l, true); - assertSessionExists(3l); + createSession(3L, true); + assertSessionExists(3L); } @Test 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 c89c2f23873..99ef1831744 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 @@ -62,7 +62,7 @@ public class RemoteSessionTest { public void require_that_session_is_initialized() { Clock clock = Clock.systemUTC(); Session session = createSession(2, clock); - assertThat(session.getSessionId(), is(2l)); + assertThat(session.getSessionId(), is(2L)); session = createSession(Long.MAX_VALUE, clock); assertThat(session.getSessionId(), is(Long.MAX_VALUE)); } @@ -73,14 +73,14 @@ public class RemoteSessionTest { session.loadPrepared(); ApplicationSet applicationSet = session.ensureApplicationLoaded(); assertNotNull(applicationSet); - assertThat(applicationSet.getApplicationGeneration(), is(3l)); + assertThat(applicationSet.getApplicationGeneration(), is(3L)); assertThat(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application().value(), is("foo")); assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); session.deactivate(); applicationSet = session.ensureApplicationLoaded(); assertNotNull(applicationSet); - assertThat(applicationSet.getApplicationGeneration(), is(3l)); + assertThat(applicationSet.getApplicationGeneration(), is(3L)); assertThat(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application().value(), is("foo")); assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); } @@ -224,9 +224,7 @@ public class RemoteSessionTest { .curator(curator) .clock(clock) .modelFactoryRegistry(new ModelFactoryRegistry(modelFactories)); - if (permanentApplicationPackage.isPresent()) - registryBuilder.permanentApplicationPackage(permanentApplicationPackage.get()); - + permanentApplicationPackage.ifPresent(registryBuilder::permanentApplicationPackage); return new RemoteSession(tenantName, sessionId, registryBuilder.build(), zkc); } @@ -234,12 +232,12 @@ public class RemoteSessionTest { private class MockModelFactory implements ModelFactory { /** Throw a RuntimeException on load - this is handled gracefully during model building */ - public boolean throwOnLoad = false; + boolean throwOnLoad = false; /** Throw an Error on load - this is useful to propagate this condition all the way to the test */ - public boolean throwErrorOnLoad = false; + boolean throwErrorOnLoad = false; - public ModelContext modelContext; + ModelContext modelContext; public Version vespaVersion = new Version(1, 2, 3); /** The validation overrides of this, or null if none */ @@ -247,9 +245,9 @@ public class RemoteSessionTest { private Clock clock = Clock.fixed(LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(), ZoneOffset.UTC); - public MockModelFactory() { this(null); } + MockModelFactory() { this(null); } - public MockModelFactory(String validationOverrides) { + MockModelFactory(String validationOverrides) { this.validationOverrides = validationOverrides; } @@ -271,7 +269,7 @@ public class RemoteSessionTest { return loadModel(); } - public Model loadModel() { + Model loadModel() { try { ApplicationPackage application = new MockApplicationPackage.Builder().withEmptyHosts().withEmptyServices().withValidationOverrides(validationOverrides).build(); DeployState deployState = new DeployState.Builder().applicationPackage(application).now(clock.instant()).build(); 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 651dde375ee..e219c516f95 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 @@ -9,14 +9,22 @@ import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.CertificateNotReadyException; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.HostFilter; +import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.ProvisionLogger; +import com.yahoo.config.provision.Provisioner; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.TenantName; +import com.yahoo.config.provision.exception.LoadBalancerServiceException; import com.yahoo.io.IOUtils; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.slime.Slime; +import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.MockSecretStore; import com.yahoo.vespa.config.server.TestComponentRegistry; @@ -45,6 +53,7 @@ import java.io.File; import java.io.IOException; import java.time.Instant; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -294,6 +303,13 @@ public class SessionPreparerTest { prepare(new File("src/test/resources/deploy/hosted-app"), params); } + @Test(expected = LoadBalancerServiceException.class) + public void require_that_conflict_is_returned_when_creating_load_balancer_fails() throws IOException { + preparer = createPreparer(HostProvisionerProvider.withProvisioner(new FailWithTransientExceptionProvisioner())); + var params = new PrepareParams.Builder().applicationId(applicationId("test")).build(); + prepare(new File("src/test/resources/deploy/hosted-app"), params); + } + private void prepare(File app) throws IOException { prepare(app, new PrepareParams.Builder().build()); } @@ -327,4 +343,22 @@ public class SessionPreparerTest { ApplicationName.from(applicationName), InstanceName.defaultName()); } + private static class FailWithTransientExceptionProvisioner implements Provisioner { + + @Override + public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { + throw new LoadBalancerServiceException("Unable to create load balancer", new Exception("some internal exception")); + } + + @Override + public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) { } + + @Override + public void remove(NestedTransaction transaction, ApplicationId application) { } + + @Override + public void restart(ApplicationId application, HostFilter filter) { } + + } + } 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 01cb90721f3..b8a3d0bc401 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 @@ -12,7 +12,6 @@ import static org.junit.Assert.assertThat; /** * @author hmusum - * @since 5.1.14 */ public class SessionRepoTest { @Test @@ -20,7 +19,7 @@ public class SessionRepoTest { SessionRepo<TestSession> sessionRepo = new SessionRepo<>(); assertNull(sessionRepo.getSession(1L)); sessionRepo.addSession(new TestSession(1)); - assertThat(sessionRepo.getSession(1L).getSessionId(), is(1l)); + assertThat(sessionRepo.getSession(1L).getSessionId(), is(1L)); } @Test(expected = IllegalArgumentException.class) diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java index 522a21a47b3..27e04bd7422 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java @@ -92,18 +92,18 @@ public class SessionZooKeeperClientTest { public void require_that_create_time_can_be_written_and_read() { SessionZooKeeperClient zkc = createSessionZKClient("3"); curator.delete(Path.fromString("3")); - assertThat(zkc.readCreateTime(), is(0l)); - zkc.createNewSession(123456l, TimeUnit.SECONDS); - assertThat(zkc.readCreateTime(), is(123456l)); + assertThat(zkc.readCreateTime(), is(0L)); + zkc.createNewSession(123456L, TimeUnit.SECONDS); + assertThat(zkc.readCreateTime(), is(123456L)); } @Test public void require_that_create_time_has_correct_unit() { SessionZooKeeperClient zkc = createSessionZKClient("3"); curator.delete(Path.fromString("3")); - assertThat(zkc.readCreateTime(), is(0l)); + assertThat(zkc.readCreateTime(), is(0L)); zkc.createNewSession(60, TimeUnit.MINUTES); - assertThat(zkc.readCreateTime(), is(3600l)); + assertThat(zkc.readCreateTime(), is(3600L)); } private void assertApplicationIdParse(String sessionId, String idString, String expectedIdString) { 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 36bb7a926b5..ff3d9449448 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 @@ -27,7 +27,6 @@ import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.Application; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer; -import com.yahoo.vespa.config.server.host.HostRegistries; import com.yahoo.vespa.config.server.model.TestModelFactory; import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; @@ -135,24 +134,24 @@ public class TenantRequestHandlerTest { new TestModelFactory(new Version(3, 2, 1)))); } - public <T extends ConfigInstance> T resolve(Class<T> clazz, - TenantRequestHandler tenantRequestHandler, - ApplicationId appId, - Version vespaVersion, - String configId) { + private <T extends ConfigInstance> T resolve(Class<T> clazz, + TenantRequestHandler tenantRequestHandler, + ApplicationId appId, + Version vespaVersion, + String configId) { ConfigResponse response = getConfigResponse(clazz, tenantRequestHandler, appId, vespaVersion, configId); return ConfigPayload.fromUtf8Array(response.getPayload()).toInstance(clazz, configId); } - public <T extends ConfigInstance> ConfigResponse getConfigResponse(Class<T> clazz, - TenantRequestHandler tenantRequestHandler, - ApplicationId appId, - Version vespaVersion, - String configId) { + private <T extends ConfigInstance> ConfigResponse getConfigResponse(Class<T> clazz, + TenantRequestHandler tenantRequestHandler, + ApplicationId appId, + Version vespaVersion, + String configId) { return tenantRequestHandler.resolveConfig(appId, new GetConfigRequest() { @Override public ConfigKey<T> getConfigKey() { - return new ConfigKey<T>(clazz, configId); + return new ConfigKey<>(clazz, configId); } @Override @@ -183,7 +182,7 @@ public class TenantRequestHandlerTest { // Using only payload list for this simple test SimpletypesConfig config = resolve(SimpletypesConfig.class, server, defaultApp(), vespaVersion, ""); assertThat(config.intval(), is(1337)); - assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1l)); + assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1L)); server.reloadConfig(reloadConfig(1L)); ConfigResponse configResponse = getConfigResponse(SimpletypesConfig.class, server, defaultApp(), vespaVersion, ""); @@ -191,7 +190,7 @@ public class TenantRequestHandlerTest { config = resolve(SimpletypesConfig.class, server, defaultApp(), vespaVersion, ""); assertThat(config.intval(), is(1337)); assertThat(listener.reloaded.get(), is(2)); - assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1l)); + assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1L)); assertThat(listener.tenantHosts.size(), is(1)); assertThat(server.resolveApplicationId("mytesthost"), is(applicationId)); @@ -204,7 +203,7 @@ public class TenantRequestHandlerTest { config = resolve(SimpletypesConfig.class, server, defaultApp(), vespaVersion,""); assertThat(config.intval(), is(1330)); assertThat(listener.reloaded.get(), is(1)); - assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(2l)); + assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(2L)); } @Test @@ -284,7 +283,7 @@ public class TenantRequestHandlerTest { } public static class MockReloadListener implements ReloadListener { - public AtomicInteger reloaded = new AtomicInteger(0); + AtomicInteger reloaded = new AtomicInteger(0); AtomicInteger removed = new AtomicInteger(0); Map<String, Collection<String>> tenantHosts = new LinkedHashMap<>(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java index e140dae3650..5b8f5299c01 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantTest.java @@ -6,7 +6,6 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.config.server.MockReloadHandler; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.TenantApplications; -import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Before; import org.junit.Test; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java index 888cbb7a68b..f745e023126 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java @@ -20,7 +20,7 @@ public class InitializedCounterTest { configCurator.createNode("/sessions/2"); InitializedCounter counter = new InitializedCounter(configCurator, "/counter", "/sessions"); - assertThat(counter.counter.get(), is(2l)); + assertThat(counter.counter.get(), is(2L)); } } diff --git a/configserver/src/test/resources/configdefinitions/datastructures.def b/configserver/src/test/resources/configdefinitions/datastructures.def deleted file mode 100644 index 79b3a8e77a0..00000000000 --- a/configserver/src/test/resources/configdefinitions/datastructures.def +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -namespace=config - -date[] string - -stock[].ticker string -stock[].type enum { COMMON, ETF, ETC } default=COMMON -stock[].volume[] int diff --git a/configserver/src/test/resources/configdefinitions/function-test.def b/configserver/src/test/resources/configdefinitions/function-test.def deleted file mode 100644 index b2a27c42285..00000000000 --- a/configserver/src/test/resources/configdefinitions/function-test.def +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -# -# This def file should test most aspects of def files that makes a difference -# for the autogenerated config classes. The goal is to trigger all blocks of -# code in the code generators. This includes: -# -# - Use all legal special characters in the def file name, to ensure that those -# that needs to be replaced in type names are actually replaced. -# - Use the same enum type twice to verify that we dont declare or define it -# twice. -# - Use the same struct type twice for the same reason. -# - Include arrays of primitives and structs. -# - Include enum primitives and array of enums. Arrays of enums must be handled -# specially by the C++ code. -# - Include enums both with and without default values. -# - Include primitive string, numbers & doubles both with and without default -# values. -# - Have an array within a struct, to verify that we correctly recurse. -# - Reuse type name further within to ensure that this works. - -namespace=config - -# Some random bool without a default value. These comments exist to check - # that comment parsing works. -bool_val bool - ## A bool with a default value set. -bool_with_def bool default=false -int_val int -int_with_def int default=-545 -double_val double -double_with_def double default=-6.43 -# Another comment -string_val string -stringwithdef string default="foobar" -enum_val enum { FOO, BAR, FOOBAR } -enumwithdef enum { FOO2, BAR2, FOOBAR2 } default=BAR2 -refval reference -refwithdef reference default=":parent:" - -boolarr[] bool -intarr[] int -doublearr[] double -stringarr[] string -enumarr[] enum { ARRAY, VALUES } -refarr[] reference - - -myarray[].intval int default=14 -myarray[].stringval[] string -myarray[].enumval enum { INNER, ENUM, TYPE } default=TYPE -myarray[].refval reference # Value in array without default -myarray[].anotherarray[].foo int default=-4 - diff --git a/configserver/src/test/resources/configdefinitions/unicode.def b/configserver/src/test/resources/configdefinitions/unicode.def deleted file mode 100644 index e3b93671e99..00000000000 --- a/configserver/src/test/resources/configdefinitions/unicode.def +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -namespace=config - -unicodestring1 string -unicodestring2 string default="abc æøå 囲碁 ÆØÅ ABC" diff --git a/configserver/src/test/resources/metrics/container_metrics b/configserver/src/test/resources/metrics/container_metrics new file mode 100644 index 00000000000..a6a5828934c --- /dev/null +++ b/configserver/src/test/resources/metrics/container_metrics @@ -0,0 +1,41 @@ +{ + "services": [ + { + "name":"vespa.container", + "timestamp": 1557306075, + "metrics": [ + { + "values": { + "queries.rate": 23.0, + "query_latency.sum": 2000, + "document.count": 300000, + "feed.rate": 23.0, + "write_latency.sum": 2000 + } + }, + { + "values": { + "query_latency.count": 43.0, + "query_latency.sum": 3000, + "feed_latency.count": 43.0, + "feed_latency.sum": 3000 + + } + } + ] + }, + + { + "name":"vespa.qrserver", + "timestamp": 1557306075, + "metrics": [ + { + "values": { + "query_latency.count": 43.0, + "query_latency.sum": 3000 + } + } + ] + } + ] +}
\ No newline at end of file diff --git a/configserver/src/test/resources/metrics/content_metrics b/configserver/src/test/resources/metrics/content_metrics new file mode 100644 index 00000000000..c881e574f8b --- /dev/null +++ b/configserver/src/test/resources/metrics/content_metrics @@ -0,0 +1,16 @@ +{ + "services": [ + { + "name":"vespa.distributor", + "timestamp": 1557306075, + "metrics": [ + { + "values": { + "vds.distributor.docsstored.average": 3000 + + } + } + ] + } + ] +}
\ No newline at end of file diff --git a/container-dependency-versions/pom.xml b/container-dependency-versions/pom.xml index 0c27d074871..d18e6eeb13e 100644 --- a/container-dependency-versions/pom.xml +++ b/container-dependency-versions/pom.xml @@ -447,7 +447,7 @@ <properties> <bouncycastle.version>1.58</bouncycastle.version> - <felix.version>6.0.2</felix.version> + <felix.version>6.0.3</felix.version> <findbugs.version>1.3.9</findbugs.version> <guava.version>20.0</guava.version> <guice.version>3.0</guice.version> diff --git a/container-di/src/main/java/com/yahoo/container/di/componentgraph/core/ComponentGraph.java b/container-di/src/main/java/com/yahoo/container/di/componentgraph/core/ComponentGraph.java index 4a0598d3436..cdd886b4672 100644 --- a/container-di/src/main/java/com/yahoo/container/di/componentgraph/core/ComponentGraph.java +++ b/container-di/src/main/java/com/yahoo/container/di/componentgraph/core/ComponentGraph.java @@ -13,6 +13,8 @@ import com.yahoo.component.ComponentId; import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.config.ConfigInstance; import com.yahoo.container.di.componentgraph.Provider; +import com.yahoo.container.di.componentgraph.cycle.CycleFinder; +import com.yahoo.container.di.componentgraph.cycle.Graph; import com.yahoo.log.LogLevel; import com.yahoo.vespa.config.ConfigKey; import net.jcip.annotations.NotThreadSafe; @@ -372,13 +374,19 @@ public class ComponentGraph { /** * The returned list is the nodes from the graph bottom-up. * + * For each iteration, the algorithm finds the components that are not "wanted by" any other component, + * and prepends those components into the resulting 'sorted' list. Hence, the first element in the returned + * list is the component that is directly or indirectly wanted by "most" other components. + * * @return A list where a earlier than b in the list implies that there is no path from a to b */ private static List<Node> topologicalSort(Collection<Node> nodes) { Map<ComponentId, Integer> numIncoming = new HashMap<>(); nodes.forEach( - node -> node.usedComponents().forEach(injectedNode -> numIncoming.merge(injectedNode.componentId(), 1, (a, b) -> a + b))); + node -> node.usedComponents().forEach( + injectedNode -> numIncoming.merge(injectedNode.componentId(), 1, (a, b) -> a + b))); + LinkedList<Node> sorted = new LinkedList<>(); List<Node> unsorted = new ArrayList<>(nodes); @@ -394,7 +402,7 @@ public class ComponentGraph { }); if (ready.isEmpty()) { - throw new IllegalStateException("There is a cycle in the component injection graph."); + throw new IllegalStateException("There is a cycle in the component injection graph: " + findCycle(notReady)); } ready.forEach(node -> node.usedComponents() @@ -404,4 +412,16 @@ public class ComponentGraph { } return sorted; } + + private static List<String> findCycle(List<Node> nodes) { + var cyclicGraph = new Graph<String>(); + for (var node : nodes) { + for (var adjacent : node.usedComponents()) { + cyclicGraph.edge(node.componentId().stringValue(), + adjacent.componentId().stringValue()); + } + } + return new CycleFinder<>(cyclicGraph).findCycle(); + } + } diff --git a/container-di/src/main/java/com/yahoo/container/di/componentgraph/cycle/CycleFinder.java b/container-di/src/main/java/com/yahoo/container/di/componentgraph/cycle/CycleFinder.java new file mode 100644 index 00000000000..3b29fa0a04f --- /dev/null +++ b/container-di/src/main/java/com/yahoo/container/di/componentgraph/cycle/CycleFinder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package com.yahoo.container.di.componentgraph.cycle; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static com.yahoo.container.di.componentgraph.cycle.CycleFinder.State.BLACK; +import static com.yahoo.container.di.componentgraph.cycle.CycleFinder.State.GRAY; +import static com.yahoo.container.di.componentgraph.cycle.CycleFinder.State.WHITE; +import static com.yahoo.log.LogLevel.DEBUG; +import static java.util.Collections.singletonList; + + +/** + * <p>Applies the + * <a href="https://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/"> three-color algorithm</a> + * to detect a cycle in a directed graph. If there are multiple cycles, this implementation only detects one + * of them and does not guarantee that the shortest cycle is found. + * </p> + * + * @author gjoranv + */ +public class CycleFinder<T> { + private static final Logger log = Logger.getLogger(CycleFinder.class.getName()); + + enum State { + WHITE, GRAY, BLACK; + } + + private final Graph<T> graph; + + private Map<T, State> colors; + + private List<T> cycle; + + public CycleFinder(Graph<T> graph) { + this.graph = graph; + } + + private void resetState() { + cycle = null; + colors = new LinkedHashMap<>(); + graph.getVertices().forEach(v -> colors.put(v, WHITE)); + } + + /** + * Returns a list of vertices constituting a cycle in the graph, or an empty + * list if no cycle was found. Only the first encountered cycle is returned. + */ + public List<T> findCycle() { + resetState(); + for (T vertex : graph.getVertices()) { + if (colors.get(vertex) == WHITE) { + if (visitDepthFirst(vertex, new ArrayList<>(singletonList(vertex)))) { + if (cycle == null) throw new IllegalStateException("Null cycle - this should never happen"); + if (cycle.isEmpty()) throw new IllegalStateException("Empty cycle - this should never happen"); + log.log(DEBUG, "Cycle detected: " + cycle); + return cycle; + } + } + } + return new ArrayList<>(); + } + + private boolean visitDepthFirst(T vertex, List<T> path) { + colors.put(vertex, GRAY); + log.log(DEBUG, "Vertex start " + vertex + " - colors: " + colors + " - path: " + path); + for (T adjacent : graph.getAdjacent(vertex)) { + path.add(adjacent); + if (colors.get(adjacent) == GRAY) { + cycle = removePathIntoCycle(path); + return true; + } + if (colors.get(adjacent) == WHITE && visitDepthFirst(adjacent, path)) { + return true; + } + path.remove(adjacent); + } + colors.put(vertex, BLACK); + log.log(DEBUG, "Vertex end " + vertex + " - colors: " + colors + " - path: " + path); + return false; + } + + private List<T> removePathIntoCycle(List<T> pathWithCycle) { + T cycleStart = pathWithCycle.get(pathWithCycle.size() - 1); + return pathWithCycle.stream() + .dropWhile(vertex -> ! vertex.equals(cycleStart)) + .collect(Collectors.toList()); + } + +} diff --git a/container-di/src/main/java/com/yahoo/container/di/componentgraph/cycle/Graph.java b/container-di/src/main/java/com/yahoo/container/di/componentgraph/cycle/Graph.java new file mode 100644 index 00000000000..e1b110d51ee --- /dev/null +++ b/container-di/src/main/java/com/yahoo/container/di/componentgraph/cycle/Graph.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package com.yahoo.container.di.componentgraph.cycle; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * Class representing a directed graph. + * + * @author gjoranv + */ +public class Graph<T> { + + private final Map<T, LinkedHashSet<T>> adjMap = new LinkedHashMap<>(); + + public void edge(T from, T to) { + if (from == null || to == null) + throw new IllegalArgumentException("Null vertices are not allowed, edge: " + from + "->" + to); + + adjMap.computeIfAbsent(from, k -> new LinkedHashSet<>()).add(to); + adjMap.computeIfAbsent(to, k -> new LinkedHashSet<>()); + } + + Set<T> getVertices() { + return adjMap.keySet(); + } + + /** + * Returns the outgoing edges of the given vertex. + */ + Set<T> getAdjacent(T vertex) { + return adjMap.get(vertex); + } + + private void throwIfMissingVertex(T vertex) { + if (! adjMap.containsKey(vertex)) throw new IllegalArgumentException("No such vertex in the graph: " + vertex); + } +} diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java index a5934bcf098..8d323233ef5 100644 --- a/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java +++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java @@ -323,6 +323,7 @@ public class ComponentGraphTest { fail("Cycle exception expected."); } catch (Throwable e) { assertThat(e.getMessage(), containsString("cycle")); + assertThat(e.getMessage(), containsString("ComponentCausingCycle")); } } diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java new file mode 100644 index 00000000000..219aa6b5e8b --- /dev/null +++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package com.yahoo.container.di.componentgraph.cycle; + +import org.junit.Test; + +import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.A; +import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.B; +import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.C; +import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.D; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertThat; + +/** + * @author gjoranv + */ +public class CycleFinderTest { + + enum Vertices {A, B, C, D} + + @Test + public void graph_without_cycles_returns_no_cycle() { + var graph = new Graph<Vertices>(); + graph.edge(A, B); + graph.edge(B, C); + graph.edge(A, C); + graph.edge(D, A); + + var cycleFinder = new CycleFinder<>(graph); + assertThat(cycleFinder.findCycle(), empty()); + } + + @Test + public void graph_with_cycle_returns_cycle() { + var graph = new Graph<Vertices>(); + graph.edge(A, B); + graph.edge(B, C); + graph.edge(C, A); + + var cycleFinder = new CycleFinder<>(graph); + assertThat(cycleFinder.findCycle(), contains(A, B, C, A)); + } + + @Test + public void graph_with_self_referencing_vertex_returns_cycle() { + var graph = new Graph<Vertices>(); + graph.edge(A, A); + + var cycleFinder = new CycleFinder<>(graph); + assertThat(cycleFinder.findCycle(), contains(A, A)); + } + + @Test + public void leading_nodes_are_stripped_from_cycle() { + var graph = new Graph<Vertices>(); + graph.edge(A, B); + graph.edge(B, C); + graph.edge(C, B); + + var cycleFinder = new CycleFinder<>(graph); + assertThat(cycleFinder.findCycle(), contains(B, C, B)); + } + + @Test + public void findCycle_is_idempotent_with_cycle() { + var graph = new Graph<Vertices>(); + graph.edge(A, A); + + var cycleFinder = new CycleFinder<>(graph); + assertThat(cycleFinder.findCycle(), contains(A, A)); + assertThat(cycleFinder.findCycle(), contains(A, A)); + } + + @Test + public void findCycle_is_idempotent_without_cycle() { + var graph = new Graph<Vertices>(); + graph.edge(A, B); + + var cycleFinder = new CycleFinder<>(graph); + assertThat(cycleFinder.findCycle(), empty()); + assertThat(cycleFinder.findCycle(), empty()); + } + +} diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java new file mode 100644 index 00000000000..588c1e30ffe --- /dev/null +++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package com.yahoo.container.di.componentgraph.cycle; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static com.yahoo.container.di.componentgraph.cycle.GraphTest.Vertices.A; +import static com.yahoo.container.di.componentgraph.cycle.GraphTest.Vertices.B; +import static com.yahoo.container.di.componentgraph.cycle.GraphTest.Vertices.C; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertThat; + +/** + * @author gjoranv + */ +public class GraphTest { + + enum Vertices {A, B, C} + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void vertices_and_edges_are_added_and_can_be_retrieved() { + var graph = new Graph<Vertices>(); + graph.edge(A, B); + graph.edge(B, C); + graph.edge(A, C); + + assertThat(graph.getVertices().size(), is(3)); + assertThat(graph.getAdjacent(A), containsInAnyOrder(B, C)); + assertThat(graph.getAdjacent(B), containsInAnyOrder(C)); + assertThat(graph.getAdjacent(C), empty()); + } + + @Test + public void null_vertices_are_not_allowed() { + var graph = new Graph<Vertices>(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Null vertices are not allowed"); + graph.edge(A, null); + } + + @Test + public void duplicate_edges_are_ignored() { + var graph = new Graph<Vertices>(); + graph.edge(A, B); + graph.edge(A, B); + + assertThat(graph.getAdjacent(A).size(), is(1)); + } + + @Test + public void self_edges_are_allowed() { + var graph = new Graph<Vertices>(); + graph.edge(A, A); + + assertThat(graph.getAdjacent(A), contains(A)); + } + +} diff --git a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java index 8e654bf34b8..45303a3c646 100644 --- a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java +++ b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java @@ -99,6 +99,8 @@ public class SearchHandler extends LoggingRequestHandler { private final ExecutionFactory executionFactory; + private final boolean enableGroupingSessionCache; + private final class MeanConnections implements Callback { @Override @@ -122,6 +124,7 @@ public class SearchHandler extends LoggingRequestHandler { ExecutionFactory executionFactory) { super(executor, accessLog, metric, true); log.log(LogLevel.DEBUG, "SearchHandler.init " + System.identityHashCode(this)); + this.enableGroupingSessionCache = queryProfileConfig.enableGroupingSessionCache(); QueryProfileRegistry queryProfileRegistry = QueryProfileConfigurer.createFromConfig(queryProfileConfig); this.queryProfileRegistry = queryProfileRegistry.compile(); this.executionFactory = executionFactory; @@ -232,6 +235,9 @@ public class SearchHandler extends LoggingRequestHandler { CompiledQueryProfile queryProfile = queryProfileRegistry.findQueryProfile(queryProfileName); Query query = new Query(request, requestMap, queryProfile); + if (enableGroupingSessionCache) { + query.setGroupingSessionCache(true); + } boolean benchmarking = VespaHeaders.benchmarkOutput(request); boolean benchmarkCoverage = VespaHeaders.benchmarkCoverage(benchmarking, request.getJDiscRequest().headers()); diff --git a/container-search/src/main/resources/configdefinitions/query-profiles.def b/container-search/src/main/resources/configdefinitions/query-profiles.def index 20fcbda0d72..11966cae8ce 100644 --- a/container-search/src/main/resources/configdefinitions/query-profiles.def +++ b/container-search/src/main/resources/configdefinitions/query-profiles.def @@ -86,7 +86,9 @@ queryprofiletype[].field[].mandatory bool default=false # A space-separated list of aliases of this field name. Aliases are case insensitive queryprofiletype[].field[].alias string default="" - +# Temporary feature flag for enabling grouping session cache by default +# TODO Remove me once grouping session cache rollout is complete and cache is enabled by default +enableGroupingSessionCache bool default=false diff --git a/controller-api/pom.xml b/controller-api/pom.xml index 15098a04787..eeb6425bc25 100644 --- a/controller-api/pom.xml +++ b/controller-api/pom.xml @@ -70,6 +70,12 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + </dependencies> <build> diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ClusterMetrics.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ClusterMetrics.java new file mode 100644 index 00000000000..1377a333335 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/ClusterMetrics.java @@ -0,0 +1,40 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.api.application.v4.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author olaa + */ +public class ClusterMetrics { + + private final String clusterId; + private final ClusterType clusterType; + private final Map<String, Double> metrics; + + public ClusterMetrics(String clusterId, ClusterType clusterType) { + this.clusterId = clusterId; + this.clusterType = clusterType; + this.metrics = new HashMap<>(); + } + + public String getClusterId() { + return clusterId; + } + + public ClusterType getClusterType() { + return clusterType; + } + + public Map<String, Double> getMetrics() { + return Collections.unmodifiableMap(metrics); + } + + public void addMetric(String name, double value) { + metrics.put(name, value); + } + + public enum ClusterType {content, container}; +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java index 9eae2965c45..688cf275892 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java @@ -6,6 +6,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus; +import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate; @@ -44,6 +45,8 @@ public interface ConfigServer { InputStream getLogs(DeploymentId deployment, Map<String, String> queryParameters); + List<ClusterMetrics> getMetrics(DeploymentId deployment); + List<String> getContentClusters(DeploymentId deployment); /** diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java index 20599e92aa9..780969206a2 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java @@ -38,7 +38,8 @@ public class ConfigServerException extends RuntimeException { REQUEST_TIMEOUT, UNKNOWN_VESPA_VERSION, PARENT_HOST_NOT_READY, - CERTIFICATE_NOT_READY + CERTIFICATE_NOT_READY, + LOAD_BALANCER_NOT_READY } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java index 9f686570da1..0ad15e9cabe 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/LoadBalancer.java @@ -1,15 +1,12 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.configserver; -import com.google.common.collect.ImmutableSortedSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.RotationName; import java.util.Objects; import java.util.Optional; -import java.util.Set; /** * Represents an exclusive load balancer, assigned to an application's cluster. @@ -23,16 +20,14 @@ public class LoadBalancer { private final ClusterSpec.Id cluster; private final HostName hostname; private final Optional<String> dnsZone; - private final Set<RotationName> rotations; public LoadBalancer(String id, ApplicationId application, ClusterSpec.Id cluster, HostName hostname, - Optional<String> dnsZone, Set<RotationName> rotations) { + Optional<String> dnsZone) { this.id = Objects.requireNonNull(id, "id must be non-null"); this.application = Objects.requireNonNull(application, "application must be non-null"); this.cluster = Objects.requireNonNull(cluster, "cluster must be non-null"); this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null"); this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null"); - this.rotations = ImmutableSortedSet.copyOf(Objects.requireNonNull(rotations, "rotations must be non-null")); } public String id() { @@ -55,8 +50,4 @@ public class LoadBalancer { return dnsZone; } - public Set<RotationName> rotations() { - return rotations; - } - } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/ConfigServerMetricsService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/ConfigServerMetricsService.java new file mode 100644 index 00000000000..a47e2165291 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/ConfigServerMetricsService.java @@ -0,0 +1,68 @@ +package com.yahoo.vespa.hosted.controller.api.integration.metrics; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.HostName; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; +import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Retrieves metrics from the configuration server. + * + * @author ogronnesby + */ +public class ConfigServerMetricsService implements MetricsService { + private final ConfigServer configServerClient; + + public ConfigServerMetricsService(ConfigServer configServerClient) { + this.configServerClient = configServerClient; + } + + @Override + public ApplicationMetrics getApplicationMetrics(ApplicationId application) { + // TODO(ogronnesby): How to produce these values in Public context? + return new ApplicationMetrics(0.0, 0.0); + } + + @Override + public DeploymentMetrics getDeploymentMetrics(ApplicationId application, ZoneId zone) { + var deploymentId = new DeploymentId(application, zone); + var metrics = configServerClient.getMetrics(deploymentId); + + // TODO(ogronnesby): We probably want something more intelligent than just using .sum(), but it's better to + // TODO(ogronnesby): get some values populated and then fix the formula later. + + // The field names here come from the MetricsResponse class. + + return new DeploymentMetrics( + doubleStream(metrics, "queriesPerSecond").mapToDouble(Double::doubleValue).sum(), + doubleStream(metrics, "feedPerSecond").mapToDouble(Double::doubleValue).sum(), + doubleStream(metrics, "documentCount").mapToLong(Double::longValue).sum(), + doubleStream(metrics, "queryLatency").mapToDouble(Double::doubleValue).sum(), + doubleStream(metrics, "feedLatency").mapToDouble(Double::doubleValue).sum() + ); + } + + @Override + public Map<HostName, RotationStatus> getRotationStatus(String rotationName) { + // TODO(ogronnesby): getRotationStatus doesn't really belong in this interface, and global + // TODO(ogronnesby): endpoints does not work in public yet. + return Map.of(); + } + + @Override + public Map<String, SystemMetrics> getSystemMetrics(ApplicationId application, ZoneId zone) { + // TODO(ogronnesby): Need a backing source for this data + return Map.of(); + } + + private Stream<Double> doubleStream(List<ClusterMetrics> metrics, String name) { + return metrics.stream().map(m -> m.getMetrics().getOrDefault(name, 0.0)); + } +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/MetricsService.java index 32e94625c5a..a6d7c5d0688 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/MetricsService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/MetricsService.java @@ -1,5 +1,5 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration; +package com.yahoo.vespa.hosted.controller.api.integration.metrics; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostName; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/package-info.java new file mode 100644 index 00000000000..d14f2e79882 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.vespa.hosted.controller.api.integration.metrics; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeHistory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeHistory.java index 42e2c97e515..a4691001adc 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeHistory.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeHistory.java @@ -32,6 +32,6 @@ public class NodeHistory { return event; } - public enum Agent { system, application, operator, NodeRetirer, NodeFailer } + public enum Agent { system, application, operator, NodeFailer } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java index 2731ccc9597..08c913e9085 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Issue.java @@ -105,7 +105,8 @@ public class Issue { public enum Type { defect, // A defect which needs fixing. - task // A task the humans must perform. + task, // A task the humans must perform. + operationalTask // SRE and operational tasks. } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java index 7f7a6b758d5..e3a2781142a 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshot.java @@ -1,23 +1,63 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.resource; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode; + import java.time.Instant; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * @author olaa */ public class ResourceSnapshot { - private final ResourceAllocation resourceAllocation; + private final ApplicationId applicationId; + private final double cpuCores; + private final double memoryGb; + private final double diskGb; private final Instant timestamp; - public ResourceSnapshot(ResourceAllocation resourceAllocation, Instant timestamp) { - this.resourceAllocation = resourceAllocation; + public ResourceSnapshot(ApplicationId applicationId, double cpuCores, double memoryGb, double diskGb, Instant timestamp) { + this.applicationId = applicationId; + this.cpuCores = cpuCores; + this.memoryGb = memoryGb; + this.diskGb = diskGb; this.timestamp = timestamp; } - public ResourceAllocation getResourceAllocation() { - return resourceAllocation; + public static ResourceSnapshot from(List<NodeRepositoryNode> nodes, Instant timestamp) { + Set<ApplicationId> applicationIds = nodes.stream() + .map(n -> ApplicationId.from(n.getOwner().tenant, n.getOwner().application, n.getOwner().instance)) + .collect(Collectors.toSet()); + + if (applicationIds.size() != 1) throw new IllegalArgumentException("List of nodes can only represent one application"); + + return new ResourceSnapshot( + applicationIds.iterator().next(), + nodes.stream().mapToDouble(NodeRepositoryNode::getMinCpuCores).sum(), + nodes.stream().mapToDouble(NodeRepositoryNode::getMinMainMemoryAvailableGb).sum(), + nodes.stream().mapToDouble(NodeRepositoryNode::getMinDiskAvailableGb).sum(), + timestamp + ); + } + + public ApplicationId getApplicationId() { + return applicationId; + } + + public double getCpuCores() { + return cpuCores; + } + + public double getMemoryGb() { + return memoryGb; + } + + public double getDiskGb() { + return diskGb; } public Instant getTimestamp() { diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshotConsumer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshotConsumer.java index f7f3eddb482..bb6830770e2 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshotConsumer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceSnapshotConsumer.java @@ -1,9 +1,7 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.resource; -import com.yahoo.config.provision.ApplicationId; - -import java.util.Map; +import java.util.List; /** * Consumes a snapshot of resourses allocated/used per application. @@ -12,5 +10,7 @@ import java.util.Map; */ public interface ResourceSnapshotConsumer { - public void consume(Map<ApplicationId, ResourceSnapshot> resources); + public void consume(List<ResourceSnapshot> resources); + + public List<ResourceSnapshot> getResourceSnapshots(String tenantName, String applicationName); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockResourceSnapshotConsumer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockResourceSnapshotConsumer.java index d5d7b63e933..2cd3aef1903 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockResourceSnapshotConsumer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockResourceSnapshotConsumer.java @@ -1,25 +1,29 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.stubs; -import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshotConsumer; -import java.util.Map; +import java.util.List; /** * @author olaa */ public class MockResourceSnapshotConsumer implements ResourceSnapshotConsumer { - private Map<ApplicationId, ResourceSnapshot> resources; + private List<ResourceSnapshot> resources; @Override - public void consume(Map<ApplicationId, ResourceSnapshot> resources){ + public void consume(List<ResourceSnapshot> resources){ this.resources = resources; } - public Map<ApplicationId, ResourceSnapshot> consumedResources() { - return resources; + @Override + public List<ResourceSnapshot> getResourceSnapshots(String tenantName, String applicationName) { + throw new UnsupportedOperationException(); + } + + public List<ResourceSnapshot> consumedResources() { + return this.resources; } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java index db9291cd651..475671181e3 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.zone; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; @@ -10,7 +11,6 @@ import com.yahoo.config.provision.zone.UpgradePolicy; import com.yahoo.config.provision.zone.ZoneFilter; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; @@ -56,6 +56,9 @@ public interface ZoneRegistry { /** Return the configserver's Athenz service identity */ AthenzIdentity getConfigServerAthenzIdentity(ZoneId zoneId); + /** Return the system Athenz domain */ + AthenzDomain accessControlDomain(); + /** Returns the Vespa upgrade policy to use for zones in this registry */ UpgradePolicy upgradePolicy(); diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/ConfigServerMetricsServiceTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/ConfigServerMetricsServiceTest.java new file mode 100644 index 00000000000..9c14bc641c7 --- /dev/null +++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/metrics/ConfigServerMetricsServiceTest.java @@ -0,0 +1,81 @@ +package com.yahoo.vespa.hosted.controller.api.integration.metrics; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ConfigServerMetricsServiceTest { + + private final ApplicationId applicationId = new ApplicationId.Builder() + .tenant("foo") + .applicationName("bar") + .instanceName("default") + .build(); + + private final ZoneId zoneId = ZoneId.from("prod", "us-west-1"); + + private ConfigServer configServer; + private ConfigServerMetricsService service; + + @Before + public void before() { + configServer = Mockito.mock(ConfigServer.class); + service = new ConfigServerMetricsService(configServer); + } + + @Test + public void test_returning_metrics() { + // + // Wire up the test + // + var deploymentId = new DeploymentId(applicationId, zoneId); + + var clusterMetrics1 = new ClusterMetrics("niceCluster", ClusterMetrics.ClusterType.container) {{ + addMetric("queriesPerSecond", 23.0); + addMetric("queryLatency", 1337.0); + }}; + + var clusterMetrics2 = new ClusterMetrics("alsoNiceCluster", ClusterMetrics.ClusterType.container) {{ + addMetric("queriesPerSecond", 11.0); + addMetric("queryLatency", 12.0); + }}; + + var response = List.of(clusterMetrics1, clusterMetrics2); + + Mockito.when(configServer.getMetrics(deploymentId)).thenReturn(response); + + // + // Now we can actually test stuff :( + // + var deploymentMetrics = service.getDeploymentMetrics(applicationId, zoneId); + + assertEquals(23.0 + 11.0, deploymentMetrics.queriesPerSecond(), 0.001); + assertEquals(1337.0 + 12.0, deploymentMetrics.queryLatencyMillis(), 0.001); // TODO: again, this definition of combined latency makes no sense + assertEquals(0, deploymentMetrics.documentCount()); + assertEquals(0.0, deploymentMetrics.writeLatencyMillis(), 0.001); + assertEquals(0.0, deploymentMetrics.writesPerSecond(), 0.001); + } + + @Test + public void test_not_implemented_application_metrics() { + var applicationMetrics = service.getApplicationMetrics(applicationId); + assertEquals(0.0, applicationMetrics.queryServiceQuality(), 0.001); + assertEquals(0.0, applicationMetrics.writeServiceQuality(), 0.001); + } + + @Test + public void test_not_implemented_metrics() { + assertTrue(service.getRotationStatus("foo").isEmpty()); + assertTrue(service.getSystemMetrics(applicationId, zoneId).isEmpty()); + } +} diff --git a/controller-server/pom.xml b/controller-server/pom.xml index c6c6acafe15..ae756eae1fb 100644 --- a/controller-server/pom.xml +++ b/controller-server/pom.xml @@ -107,6 +107,13 @@ <scope>provided</scope> </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>configserver-flags</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <!-- compile --> <dependency> diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java index d8b56502fc3..ad29c4b2a0e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java @@ -10,7 +10,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 60fd095eb04..677e9e960e8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -13,7 +13,9 @@ import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.athenz.api.AthenzDomain; +import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzPrincipal; +import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.flags.BooleanFlag; @@ -279,7 +281,7 @@ public class ApplicationController { /** Deploys an application. If the application does not exist it is created. */ // TODO: Get rid of the options arg - // TODO jvenstad: Split this, and choose between deployDirectly and deploy in handler, excluding internally built from the latter. + // TODO(jvenstad): Split this, and choose between deployDirectly and deploy in handler, excluding internally built from the latter. public ActivateResult deploy(ApplicationId applicationId, ZoneId zone, Optional<ApplicationPackage> applicationPackageFromDeployer, Optional<ApplicationVersion> applicationVersionFromDeployer, @@ -333,11 +335,12 @@ public class ApplicationController { validateRun(application.get(), zone, platformVersion, applicationVersion); } - // TODO: Remove this when all packages are validated upon submission, as in ApplicationApiHandler.submit(...). + // TODO(jvenstad): Remove this when all packages are validated upon submission, as in ApplicationApiHandler.submit(...). verifyApplicationIdentityConfiguration(applicationId.tenant(), applicationPackage, deployingIdentity); - // Assign global rotation + // TODO(ogronnesby): Remove feature flag and replace calls to withRotationLegacy with withRotation + // TODO(mpolden): Remove all handling of legacy endpoints once withRotationLegacy disappears if (useMultipleEndpoints.with(FetchVector.Dimension.APPLICATION_ID, application.get().id().serializedForm()).value()) { application = withRotation(application, zone); @@ -373,7 +376,7 @@ public class ApplicationController { if ( ! preferOldestVersion && ! application.get().deploymentJobs().deployedInternally() && ! zone.environment().isManuallyDeployed()) - // TODO jvenstad: Store only on submissions + // TODO(jvenstad): Store only on submissions storeWithUpdatedConfig(application, applicationPackage); } // Release application lock while doing the deployment, which is a lengthy task. @@ -469,7 +472,7 @@ public class ApplicationController { } finally { // Even if prepare fails, a load balancer may have been provisioned. Always refresh routing policies so that // any DNS updates can be propagated as early as possible. - routingPolicies.refresh(application, zone); + routingPolicies.refresh(application, applicationPackage.deploymentSpec(), zone); } } @@ -495,21 +498,16 @@ public class ApplicationController { } private List<AssignedRotation> createDefaultGlobalIdRotation(Application application, Rotation rotation) { - // This is guaranteed by .withRotationLegacy, but add this to make inspections accept the use of .get() below - assert application.deploymentSpec().globalServiceId().isPresent(); - - final Set<RegionName> regions = application.deploymentSpec().zones().stream() - .filter(zone -> zone.environment().isProduction()) - .flatMap(zone -> zone.region().stream()) - .collect(Collectors.toSet()); - - final var assignment = new AssignedRotation( + Set<RegionName> regions = application.deploymentSpec().zones().stream() + .filter(zone -> zone.environment().isProduction()) + .flatMap(zone -> zone.region().stream()) + .collect(Collectors.toSet()); + var assignment = new AssignedRotation( ClusterSpec.Id.from(application.deploymentSpec().globalServiceId().get()), EndpointId.default_(), rotation.id(), regions ); - return List.of(assignment); } @@ -517,7 +515,7 @@ public class ApplicationController { private LockedApplication withRotation(LockedApplication application, ZoneId zone) { if (zone.environment() == Environment.prod) { try (RotationLock rotationLock = rotationRepository.lock()) { - final var rotations = rotationRepository.getOrAssignRotations(application.get(), rotationLock); + var rotations = rotationRepository.getOrAssignRotations(application.get(), rotationLock); application = application.with(rotations); store(application); // store assigned rotation even if deployment fails registerAssignedRotationCnames(application.get()); @@ -528,16 +526,12 @@ public class ApplicationController { private void registerAssignedRotationCnames(Application application) { application.assignedRotations().forEach(assignedRotation -> { - final var endpoints = application - .endpointsIn(controller.system(), assignedRotation.endpointId()) - .scope(Endpoint.Scope.global); - - final var maybeRotation = rotationRepository.getRotation(assignedRotation.rotationId()); - + var endpoints = application.endpointsIn(controller.system(), assignedRotation.endpointId()) + .scope(Endpoint.Scope.global); + var maybeRotation = rotationRepository.getRotation(assignedRotation.rotationId()); maybeRotation.ifPresent(rotation -> { - endpoints.main().ifPresent(mainEndpoint -> { - registerCname(mainEndpoint.dnsName(), rotation.name()); - }); + // For rotations assigned using <endpoints/> syntax, we only register the non-legacy name in DNS. + endpoints.main().ifPresent(mainEndpoint -> registerCname(mainEndpoint.dnsName(), rotation.name())); }); }); } @@ -545,7 +539,7 @@ public class ApplicationController { private LockedApplication withApplicationCertificate(LockedApplication application) { ApplicationId applicationId = application.get().id(); - // TODO: Verify that the application is deploying to a zone where certificate provisioning is enabled + // TODO(tokle): Verify that the application is deploying to a zone where certificate provisioning is enabled boolean provisionCertificate = provisionApplicationCertificate.with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); if (provisionCertificate) { application = application.withApplicationCertificate( @@ -627,8 +621,7 @@ public class ApplicationController { .iterator()); } catch (RuntimeException e) { - log.log(Level.WARNING, "Failed to get endpoint information for " + deploymentId + ": " - + Exceptions.toMessageString(e)); + log.log(Level.WARNING, "Failed to get endpoint information for " + deploymentId, e); return Collections.emptyList(); } } @@ -640,14 +633,14 @@ public class ApplicationController { .orElse(id.applicationId().instance().isTester())) throw new NotExistsException("Deployment", id.toString()); - // TODO jvenstad: Swap to use routingPolicies first, when this is ready. + // TODO(jvenstad): Swap to use routingPolicies first, when this is ready. try { var endpoints = routingGenerator.clusterEndpoints(id); if ( ! endpoints.isEmpty()) return endpoints; } catch (RuntimeException e) { - log.log(Level.WARNING, "Failed to get endpoint information for " + id + ": " + Exceptions.toMessageString(e)); + log.log(Level.WARNING, "Failed to get endpoint information for " + id, e); } return routingPolicies.get(id).stream() .filter(policy -> policy.endpointIn(controller.system()).scope() == Endpoint.Scope.zone) @@ -699,12 +692,12 @@ public class ApplicationController { applicationStore.removeAll(TesterId.of(id)); application.get().assignedRotations().forEach(assignedRotation -> { - final var endpoints = application.get().endpointsIn(controller.system(), assignedRotation.endpointId()); + var endpoints = application.get().endpointsIn(controller.system(), assignedRotation.endpointId()); endpoints.asList().stream() - .map(Endpoint::dnsName) - .forEach(name -> { - controller.nameServiceForwarder().removeRecords(Record.Type.CNAME, RecordName.from(name), Priority.normal); - }); + .map(Endpoint::dnsName) + .forEach(name -> { + controller.nameServiceForwarder().removeRecords(Record.Type.CNAME, RecordName.from(name), Priority.normal); + }); }); log.info("Deleted " + application); @@ -789,7 +782,7 @@ public class ApplicationController { } catch (NotFoundException ignored) { // ok; already gone } finally { - routingPolicies.refresh(application.get().id(), zone); + routingPolicies.refresh(application.get().id(), application.get().deploymentSpec(), zone); } return application.withoutDeploymentIn(zone); } @@ -861,14 +854,17 @@ public class ApplicationController { /** * Verifies that the application can be deployed to the tenant, following these rules: * - * 1. If the principal is given, verify that the principal is tenant admin or admin of the tenant domain - * 2. If the principal is not given, verify that the Athenz domain of the tenant equals Athenz domain given in deployment.xml + * 1. Verify that the Athenz service can be launched by the config server + * 2. If the principal is given, verify that the principal is tenant admin or admin of the tenant domain + * 3. If the principal is not given, verify that the Athenz domain of the tenant equals Athenz domain given in deployment.xml * * @param tenantName Tenant where application should be deployed * @param applicationPackage Application package * @param deployer Principal initiating the deployment, possibly empty */ public void verifyApplicationIdentityConfiguration(TenantName tenantName, ApplicationPackage applicationPackage, Optional<Principal> deployer) { + verifyAllowedLaunchAthenzService(applicationPackage.deploymentSpec()); + applicationPackage.deploymentSpec().athenzDomain().ifPresent(identityDomain -> { Tenant tenant = controller.tenants().require(tenantName); deployer.filter(AthenzPrincipal.class::isInstance) @@ -894,6 +890,25 @@ public class ApplicationController { }); } + /* + * Verifies that the configured athenz service (if any) can be launched. + */ + private void verifyAllowedLaunchAthenzService(DeploymentSpec deploymentSpec) { + deploymentSpec.athenzDomain().ifPresent(athenzDomain -> { + controller.zoneRegistry().zones().reachable().ids() + .forEach(zone -> { + AthenzIdentity configServerAthenzIdentity = controller.zoneRegistry().getConfigServerAthenzIdentity(zone); + deploymentSpec.athenzService(zone.environment(), zone.region()) + .map(service -> new AthenzService(athenzDomain.value(), service.value())) + .ifPresent(service -> { + boolean allowedToLaunch = ((AthenzFacade) accessControl).canLaunch(configServerAthenzIdentity, service); + if (!allowedToLaunch) + throw new IllegalArgumentException("Not allowed to launch Athenz service " + service.getFullName()); + }); + }); + }); + } + /** Returns the latest known version within the given major. */ private Optional<Version> lastCompatibleVersion(int targetMajorVersion) { return controller.versionStatus().versions().stream() diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index 08c95d1ecab..ff27a92de9a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -12,7 +12,7 @@ import com.yahoo.config.provision.zone.ZoneApi; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.hosted.controller.api.integration.BuildService; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService; import com.yahoo.vespa.hosted.controller.api.integration.RunDataStore; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateProvider; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java index 294dc10d0bd..c7607a7a422 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java @@ -8,8 +8,8 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; import com.yahoo.vespa.curator.Lock; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; @@ -25,7 +25,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.RotationStatus; -import com.yahoo.vespa.hosted.controller.rotation.RotationId; import java.time.Instant; import java.util.LinkedHashMap; @@ -279,7 +278,6 @@ public class LockedApplication { metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate); } - /** Don't expose non-leaf sub-objects. */ private LockedApplication with(Deployment deployment) { Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java index ec13066d069..e23230b8503 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java @@ -72,7 +72,7 @@ public class AssignedRotation { public static AssignedRotation fromStrings(String clusterId, String endpointId, String rotationId, Collection<String> regions) { return new AssignedRotation( new ClusterSpec.Id(clusterId), - new EndpointId(endpointId), + EndpointId.of(endpointId), new RotationId(rotationId), regions.stream().map(RegionName::from).collect(Collectors.toSet()) ); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java index 5dccd5c8120..4041c955cc4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; @@ -216,7 +215,6 @@ public class Endpoint { private ZoneId zone; private ClusterSpec.Id cluster; - private RotationName rotation; private EndpointId endpointId; private Port port; private boolean legacy = false; @@ -228,7 +226,7 @@ public class Endpoint { /** Sets the cluster and zone target of this */ public EndpointBuilder target(ClusterSpec.Id cluster, ZoneId zone) { - if (rotation != null || endpointId != null) { + if (endpointId != null) { throw new IllegalArgumentException("Cannot set multiple target types"); } this.cluster = cluster; @@ -236,18 +234,9 @@ public class Endpoint { return this; } - /** Sets the rotation target of this */ - public EndpointBuilder target(RotationName rotation) { - if ((cluster != null && zone != null) || endpointId != null) { - throw new IllegalArgumentException("Cannot set multiple target types"); - } - this.rotation = rotation; - return this; - } - /** Sets the endpoint ID as defines in deployments.xml */ public EndpointBuilder named(EndpointId endpointId) { - if (rotation != null || cluster != null || zone != null) { + if (cluster != null || zone != null) { throw new IllegalArgumentException("Cannot set multiple target types"); } this.endpointId = endpointId; @@ -277,8 +266,6 @@ public class Endpoint { String name; if (cluster != null && zone != null) { name = cluster.value(); - } else if (rotation != null) { - name = rotation.value(); } else if (endpointId != null) { name = endpointId.id(); } else { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java index 13c242c7b5f..7c88b94a2ae 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java @@ -1,5 +1,7 @@ package com.yahoo.vespa.hosted.controller.application; +import org.jetbrains.annotations.NotNull; + import java.util.Objects; /** @@ -8,12 +10,13 @@ import java.util.Objects; * * @author ogronnesby */ -public class EndpointId { +public class EndpointId implements Comparable<EndpointId> { + private static final EndpointId DEFAULT = new EndpointId("default"); private final String id; - public EndpointId(String id) { + private EndpointId(String id) { this.id = requireNotEmpty(id); } @@ -50,4 +53,10 @@ public class EndpointId { public static EndpointId default_() { return DEFAULT; } public static EndpointId of(String id) { return new EndpointId(id); } + + @Override + public int compareTo(@NotNull EndpointId o) { + return id.compareTo(o.id); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java index d9aea783880..c4613db27d1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.hosted.controller.application.Endpoint.Port; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java index c9378e27b61..7b0ec3d27ba 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java @@ -2,31 +2,30 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.RotationName; import java.util.Objects; /** - * Unique identifier for a global routing table entry (application x rotation name). + * Unique identifier for a global routing table entry (application x endpoint ID). * * @author mpolden */ public class RoutingId { private final ApplicationId application; - private final RotationName rotation; + private final EndpointId endpointId; - public RoutingId(ApplicationId application, RotationName rotation) { + public RoutingId(ApplicationId application, EndpointId endpointId) { this.application = Objects.requireNonNull(application, "application must be non-null"); - this.rotation = Objects.requireNonNull(rotation, "rotation must be non-null"); + this.endpointId = Objects.requireNonNull(endpointId, "endpointId must be non-null"); } public ApplicationId application() { return application; } - public RotationName rotation() { - return rotation; + public EndpointId endpointId() { + return endpointId; } @Override @@ -35,12 +34,12 @@ public class RoutingId { if (o == null || getClass() != o.getClass()) return false; RoutingId that = (RoutingId) o; return application.equals(that.application) && - rotation.equals(that.rotation); + endpointId.equals(that.endpointId); } @Override public int hashCode() { - return Objects.hash(application, rotation); + return Objects.hash(application, endpointId); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java index e0145e6b94c..a86bbaa317e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableSortedSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.Endpoint.Port; @@ -27,17 +26,17 @@ public class RoutingPolicy { private final ZoneId zone; private final HostName canonicalName; private final Optional<String> dnsZone; - private final Set<RotationName> rotations; + private final Set<EndpointId> endpoints; /** DO NOT USE. Public for serialization purposes */ public RoutingPolicy(ApplicationId owner, ClusterSpec.Id cluster, ZoneId zone, HostName canonicalName, - Optional<String> dnsZone, Set<RotationName> rotations) { + Optional<String> dnsZone, Set<EndpointId> endpoints) { this.owner = Objects.requireNonNull(owner, "owner must be non-null"); this.cluster = Objects.requireNonNull(cluster, "cluster must be non-null"); this.zone = Objects.requireNonNull(zone, "zone must be non-null"); this.canonicalName = Objects.requireNonNull(canonicalName, "canonicalName must be non-null"); this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null"); - this.rotations = ImmutableSortedSet.copyOf(Objects.requireNonNull(rotations, "rotations must be non-null")); + this.endpoints = ImmutableSortedSet.copyOf(Objects.requireNonNull(endpoints, "endpoints must be non-null")); } /** The application owning this */ @@ -65,9 +64,9 @@ public class RoutingPolicy { return dnsZone; } - /** The rotations in this policy */ - public Set<RotationName> rotations() { - return rotations; + /** The endpoints of this policy */ + public Set<EndpointId> endpoints() { + return endpoints; } /** Returns the endpoint of this */ @@ -77,7 +76,7 @@ public class RoutingPolicy { /** Returns rotation endpoints of this */ public EndpointList rotationEndpointsIn(SystemName system) { - return EndpointList.of(rotations.stream().map(rotation -> endpointOf(owner, rotation, system))); + return EndpointList.of(endpoints.stream().map(endpointId -> endpointOf(owner, endpointId, system))); } @Override @@ -95,14 +94,14 @@ public class RoutingPolicy { @Override public String toString() { - return String.format("%s [rotations: %s%s], %s owned by %s, in %s", canonicalName, rotations, + return String.format("%s [rotations: %s%s], %s owned by %s, in %s", canonicalName, endpoints, dnsZone.map(z -> ", DNS zone: " + z).orElse(""), cluster, owner.toShortString(), zone.value()); } /** Returns the endpoint of given rotation */ - public static Endpoint endpointOf(ApplicationId application, RotationName rotation, SystemName system) { - return Endpoint.of(application).target(rotation).on(Port.tls()).directRouting().in(system); + public static Endpoint endpointOf(ApplicationId application, EndpointId endpointId, SystemName system) { + return Endpoint.of(application).named(endpointId).on(Port.tls()).directRouting().in(system); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java index 75b7e137998..9257855eb6c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java @@ -11,6 +11,7 @@ import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.AthenzResourceName; import com.yahoo.vespa.athenz.api.AthenzRole; +import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.api.OktaAccessToken; import com.yahoo.vespa.athenz.client.zms.RoleAction; import com.yahoo.vespa.athenz.client.zms.ZmsClient; @@ -192,6 +193,10 @@ public class AthenzFacade implements AccessControl { return hasAccess("modify", service.getDomain().getName() + ":hosted-vespa", identity); } + public boolean canLaunch(AthenzIdentity principal, AthenzService service) { + return hasAccess("launch", service.getDomain().getName() + ":service."+service.getName(), principal); + } + /** * Used when creating tenancies. As there are no tenancy policies at this point, * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java index a11426b9a23..4d9296ea18d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java @@ -26,6 +26,10 @@ public class AthenzDbMock { return this; } + public Domain getOrCreateDomain(AthenzDomain domain) { + return domains.computeIfAbsent(domain, Domain::new); + } + public AthenzDbMock addHostedOperator(AthenzIdentity athenzIdentity) { hostedOperators.add(athenzIdentity); return this; @@ -37,6 +41,7 @@ public class AthenzDbMock { public final Set<AthenzIdentity> admins = new HashSet<>(); public final Set<AthenzIdentity> tenantAdmins = new HashSet<>(); public final Map<ApplicationId, Application> applications = new HashMap<>(); + public final Map<String, Service> services = new HashMap<>(); public boolean isVespaTenant = false; public Domain(AthenzDomain name) { @@ -78,4 +83,12 @@ public class AthenzDbMock { } } + public static class Service { + + public final boolean allowLaunch; + + public Service(boolean allowLaunch) { + this.allowLaunch = allowLaunch; + } + } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java index 37926d944b7..01f77795c4b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java @@ -107,6 +107,12 @@ public class ZmsClientMock implements ZmsClient { return false; } return false; + } else if ("launch".equals(action)){ + AthenzDbMock.Domain domain = getDomainOrThrow(resource.getDomain(), false); + String serviceName = resource.getEntityName().replace("service.",""); + if(!domain.services.containsKey(serviceName)) return false; + AthenzDbMock.Service service = domain.services.get(serviceName); + return service.allowLaunch; } return false; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java index ce0e7c0dbab..7ee834844af 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java @@ -12,7 +12,6 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.io.IOUtils; import com.yahoo.log.LogLevel; @@ -63,6 +62,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Con import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.BAD_REQUEST; import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.CERTIFICATE_NOT_READY; import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE; +import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.LOAD_BALANCER_NOT_READY; import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.OUT_OF_CAPACITY; import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.PARENT_HOST_NOT_READY; import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.active; @@ -125,7 +125,7 @@ public class InternalStepRunner implements StepRunner { } } catch (UncheckedIOException e) { - logger.log(INFO, "IO exception running " + id + ": " + Exceptions.toMessageString(e)); + logger.logWithInternalException(INFO, "IO exception running " + id + ": " + Exceptions.toMessageString(e), e); return Optional.empty(); } catch (RuntimeException e) { @@ -233,7 +233,8 @@ public class InternalStepRunner implements StepRunner { || e.getErrorCode() == ACTIVATION_CONFLICT || e.getErrorCode() == APPLICATION_LOCK_FAILURE || e.getErrorCode() == PARENT_HOST_NOT_READY - || e.getErrorCode() == CERTIFICATE_NOT_READY) { + || e.getErrorCode() == CERTIFICATE_NOT_READY + || e.getErrorCode() == LOAD_BALANCER_NOT_READY) { logger.log("Will retry, because of '" + e.getErrorCode() + "' deploying:\n" + e.getMessage()); return Optional.empty(); } @@ -382,7 +383,7 @@ public class InternalStepRunner implements StepRunner { private Optional<RunStatus> startTests(RunId id, DualLogger logger) { Optional<Deployment> deployment = deployment(id.application(), id.type()); - if ( ! deployment.isPresent()) { + if (deployment.isEmpty()) { logger.log(INFO, "Deployment expired before tests could start."); return Optional.of(aborted); } @@ -587,7 +588,7 @@ public class InternalStepRunner implements StepRunner { ApplicationVersion version = controller.jobController().run(id).get().versions().targetApplication(); DeploymentSpec spec = controller.applications().require(id.application()).deploymentSpec(); - byte[] servicesXml = servicesXml(controller.system(), testerFlavorFor(id, spec)); + byte[] servicesXml = servicesXml(controller.zoneRegistry().accessControlDomain(), testerFlavorFor(id, spec)); byte[] testPackage = controller.applications().applicationStore().get(id.tester(), version); ZoneId zone = id.type().zone(controller.system()); @@ -619,9 +620,7 @@ public class InternalStepRunner implements StepRunner { } /** Returns the generated services.xml content for the tester application. */ - static byte[] servicesXml(SystemName systemName, Optional<String> testerFlavor) { - String domain = systemName == SystemName.main ? "vespa.vespa" : "vespa.vespa.cd"; - + static byte[] servicesXml(AthenzDomain domain, Optional<String> testerFlavor) { String flavor = testerFlavor.orElse("d-1-4-50"); int memoryGb = Integer.parseInt(flavor.split("-")[2]); // Memory available in tester container. int jdiscMemoryPercentage = (int) Math.ceil(200.0 / memoryGb); // 2Gb memory for tester application (excessive?). @@ -646,7 +645,7 @@ public class InternalStepRunner implements StepRunner { " <http>\n" + " <server id='default' port='4080'/>\n" + " <filtering>\n" + - " <access-control domain='" + domain + "'>\n" + // Set up dummy access control to pass validation :/ + " <access-control domain='" + domain.value() + "'>\n" + // Set up dummy access control to pass validation :/ " <exclude>\n" + " <binding>http://*/tester/v1/*</binding>\n" + " </exclude>\n" + @@ -659,7 +658,7 @@ public class InternalStepRunner implements StepRunner { " </config>\n" + " <component id=\"com.yahoo.jdisc.http.filter.security.athenz.StaticRequestResourceMapper\" bundle=\"jdisc-security-filters\">\n" + " <config name=\"jdisc.http.filter.security.athenz.static-request-resource-mapper\">\n" + - " <resourceName>" + domain + ":tester-application</resourceName>\n" + + " <resourceName>" + domain.value() + ":tester-application</resourceName>\n" + " <action>deploy</action>\n" + " </config>\n" + " </component>\n" + @@ -711,6 +710,12 @@ public class InternalStepRunner implements StepRunner { log(level, message, null); } + // Print stack trace in our logs, but don't expose it to end users + private void logWithInternalException(Level level, String message, Throwable thrown) { + logger.log(level, id + " at " + step + ": " + message, thrown); + controller.jobController().log(id, step, level, message); + } + private void log(Level level, String message, Throwable thrown) { logger.log(level, id + " at " + step + ": " + message, thrown); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java index bef61dda875..068a41ed92c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java @@ -131,18 +131,21 @@ public class Versions { return change.platform().get(); return max(change.platform(), deployment.map(Deployment::version)) - .orElse(application.oldestDeployedPlatform() - .orElse(defaultVersion)); + .orElseGet(() -> application.oldestDeployedPlatform().orElse(defaultVersion)); } private static ApplicationVersion targetApplication(Application application, Change change, Optional<Deployment> deployment) { return max(change.application(), deployment.map(Deployment::applicationVersion)) - .orElse(application.oldestDeployedApplication() - .orElse(application.deploymentJobs().jobStatus().get(JobType.component) - .lastSuccess() - .get() - .application())); + .orElseGet(() -> defaultApplicationVersion(application)); + } + + private static ApplicationVersion defaultApplicationVersion(Application application) { + return application.oldestDeployedApplication() + .orElseGet(() -> Optional.ofNullable(application.deploymentJobs().jobStatus().get(JobType.component)) + .flatMap(JobStatus::lastSuccess) + .map(JobStatus.JobRun::application) + .orElse(ApplicationVersion.unknown)); } private static <T extends Comparable<T>> Optional<T> max(Optional<T> o1, Optional<T> o2) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java index 7cf710623ea..865084064a2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java @@ -7,7 +7,7 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService; import com.yahoo.vespa.hosted.controller.application.ClusterUtilization; import com.yahoo.vespa.hosted.controller.application.Deployment; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java index cd3341ed3a6..d396bfda669 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java @@ -28,6 +28,7 @@ public class DeploymentExpirer extends Maintainer { if (!isExpired(deployment)) continue; try { + log.log(Level.INFO, "Expiring deployment of " + application.id() + " in " + deployment.zone()); controller().applications().deactivate(application.id(), deployment.zone()); } catch (Exception e) { log.log(Level.WARNING, "Could not expire " + deployment + " of " + application + diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java index 4ad5940f8f2..40c16cbb8c9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java @@ -6,7 +6,7 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ApplicationController; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.RotationStatus; @@ -21,7 +21,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; @@ -41,8 +40,7 @@ public class DeploymentMetricsMaintainer extends Maintainer { private final ApplicationController applications; public DeploymentMetricsMaintainer(Controller controller, Duration duration, JobControl jobControl) { - super(controller, duration, jobControl, DeploymentMetricsMaintainer.class.getSimpleName(), - SystemName.allOf(Predicate.not(SystemName::isPublic))); + super(controller, duration, jobControl, DeploymentMetricsMaintainer.class.getSimpleName(), SystemName.all()); this.applications = controller.applications(); } @@ -79,6 +77,7 @@ public class DeploymentMetricsMaintainer extends Maintainer { .at(now); applications.store(locked.with(existingDeployment.zone(), newMetrics) .recordActivityAt(now, existingDeployment.zone())); + }); } } catch (Exception e) { @@ -122,5 +121,4 @@ public class DeploymentMetricsMaintainer extends Maintainer { default: throw new IllegalArgumentException("Unknown API value for rotation status: " + status); } } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java index 5e2ba48da3f..41460cd9fdc 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java @@ -10,22 +10,17 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeReposi import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeOwner; import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode; import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeState; -import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshotConsumer; import java.time.Clock; import java.time.Duration; -import java.time.Instant; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.function.Predicate; import java.util.stream.Collectors; /** * Creates a ResourceSnapshot per application, which is then passed on to a ResourceSnapshotConsumer - * TODO: Write JSON blob of node repo somewhere * * @author olaa */ @@ -36,8 +31,8 @@ public class ResourceMeterMaintainer extends Maintainer { private final NodeRepository nodeRepository; private final ResourceSnapshotConsumer resourceSnapshotConsumer; - private static final String metering_last_reported = "metering_last_reported"; - private static final String metering_total_reported = "metering_total_reported"; + private static final String METERING_LAST_REPORTED = "metering_last_reported"; + private static final String METERING_TOTAL_REPORTED = "metering_total_reported"; @SuppressWarnings("WeakerAccess") public ResourceMeterMaintainer(Controller controller, @@ -47,7 +42,7 @@ public class ResourceMeterMaintainer extends Maintainer { Clock clock, Metric metric, ResourceSnapshotConsumer resourceSnapshotConsumer) { - super(controller, interval, jobControl, null, SystemName.allOf(Predicate.not(SystemName::isPublic))); + super(controller, interval, jobControl, null, SystemName.all()); this.clock = clock; this.nodeRepository = nodeRepository; this.metric = metric; @@ -57,22 +52,12 @@ public class ResourceMeterMaintainer extends Maintainer { @Override protected void maintain() { List<NodeRepositoryNode> nodes = getNodes(); - Map<ApplicationId, ResourceAllocation> resourceAllocationByApplication = getResourceAllocationByApplication(nodes); - - // For now, we're only interested in resource allocation - Instant timeStamp = clock.instant(); - Map<ApplicationId, ResourceSnapshot> resourceSnapshots = resourceAllocationByApplication.entrySet().stream() - .collect(Collectors.toMap( - e -> e.getKey(), - e -> new ResourceSnapshot(e.getValue(), timeStamp)) - ); - + List<ResourceSnapshot> resourceSnapshots = getResourceSnapshots(nodes); resourceSnapshotConsumer.consume(resourceSnapshots); - metric.set(metering_last_reported, clock.millis() / 1000, metric.createContext(Collections.emptyMap())); - metric.set(metering_total_reported, resourceSnapshots.values().stream() - .map(ResourceSnapshot::getResourceAllocation) + metric.set(METERING_LAST_REPORTED, clock.millis() / 1000, metric.createContext(Collections.emptyMap())); + metric.set(METERING_TOTAL_REPORTED, resourceSnapshots.stream() .mapToDouble(r -> r.getCpuCores() + r.getMemoryGb() + r.getDiskGb()) // total metered resource usage, for alerting on drastic changes .sum() , metric.createContext(Collections.emptyMap())); @@ -88,11 +73,12 @@ public class ResourceMeterMaintainer extends Maintainer { .collect(Collectors.toList()); } - private Map<ApplicationId, ResourceAllocation> getResourceAllocationByApplication(List<NodeRepositoryNode> nodes) { + private List<ResourceSnapshot> getResourceSnapshots(List<NodeRepositoryNode> nodes) { return nodes.stream() .collect(Collectors.groupingBy( node -> applicationIdFromNodeOwner(node.getOwner()), - Collectors.collectingAndThen(Collectors.toList(), ResourceAllocation::from))); + Collectors.collectingAndThen(Collectors.toList(), nodeList -> ResourceSnapshot.from(nodeList, clock.instant())) + )).values().stream().collect(Collectors.toList()); } private ApplicationId applicationIdFromNodeOwner(NodeOwner owner) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java index 4a98cb49227..020203c6548 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java @@ -1,6 +1,7 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.maintenance; +import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.curator.Lock; @@ -12,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData; import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; import com.yahoo.vespa.hosted.controller.application.Endpoint; +import com.yahoo.vespa.hosted.controller.application.EndpointId; import com.yahoo.vespa.hosted.controller.application.RoutingId; import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority; @@ -70,14 +72,13 @@ public class RoutingPolicies { * Refresh routing policies for application in given zone. This is idempotent and changes will only be performed if * load balancers for given application have changed. */ - public void refresh(ApplicationId application, ZoneId zone) { - // TODO: Use this to decide how apply routing policies for shared routing layer + public void refresh(ApplicationId application, DeploymentSpec deploymentSpec, ZoneId zone) { if (!controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) return; var lbs = new LoadBalancers(application, zone, controller.applications().configServer() .getLoadBalancers(application, zone)); try (var lock = db.lockRoutingPolicies()) { - removeObsoleteEndpointsFromDns(lbs, lock); - storePoliciesOf(lbs, lock); + removeObsoleteEndpointsFromDns(lbs, deploymentSpec, lock); + storePoliciesOf(lbs, deploymentSpec, lock); removeObsoletePolicies(lbs, lock); registerEndpointsInDns(lbs, lock); } @@ -89,7 +90,7 @@ public class RoutingPolicies { // Create DNS record for each routing ID for (Map.Entry<RoutingId, List<RoutingPolicy>> routeEntry : routingTable.entrySet()) { - Endpoint endpoint = RoutingPolicy.endpointOf(routeEntry.getKey().application(), routeEntry.getKey().rotation(), + Endpoint endpoint = RoutingPolicy.endpointOf(routeEntry.getKey().application(), routeEntry.getKey().endpointId(), controller.system()); Set<AliasTarget> targets = routeEntry.getValue() .stream() @@ -103,10 +104,10 @@ public class RoutingPolicies { } /** Store routing policies for given route */ - private void storePoliciesOf(LoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) { + private void storePoliciesOf(LoadBalancers loadBalancers, DeploymentSpec spec, @SuppressWarnings("unused") Lock lock) { Set<RoutingPolicy> policies = new LinkedHashSet<>(get(loadBalancers.application)); for (LoadBalancer loadBalancer : loadBalancers.list) { - RoutingPolicy policy = createPolicy(loadBalancers.application, loadBalancers.zone, loadBalancer); + RoutingPolicy policy = createPolicy(loadBalancers.application, spec, loadBalancers.zone, loadBalancer); if (!policies.add(policy)) { policies.remove(policy); policies.add(policy); @@ -116,12 +117,14 @@ public class RoutingPolicies { } /** Create a policy for given load balancer and register a CNAME for it */ - private RoutingPolicy createPolicy(ApplicationId application, ZoneId zone, LoadBalancer loadBalancer) { - RoutingPolicy routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone, - loadBalancer.hostname(), loadBalancer.dnsZone(), - loadBalancer.rotations()); - RecordName name = RecordName.from(routingPolicy.endpointIn(controller.system()).dnsName()); - RecordData data = RecordData.fqdn(loadBalancer.hostname().value()); + private RoutingPolicy createPolicy(ApplicationId application, DeploymentSpec deploymentSpec, ZoneId zone, + LoadBalancer loadBalancer) { + var endpoints = endpointIdsOf(loadBalancer, zone, deploymentSpec); + var routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone, + loadBalancer.hostname(), loadBalancer.dnsZone(), + endpoints); + var name = RecordName.from(routingPolicy.endpointIn(controller.system()).dnsName()); + var data = RecordData.fqdn(loadBalancer.hostname().value()); controller.nameServiceForwarder().createCname(name, data, Priority.normal); return routingPolicy; } @@ -145,23 +148,23 @@ public class RoutingPolicies { } /** Remove unreferenced global endpoints for given route from DNS */ - private void removeObsoleteEndpointsFromDns(LoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) { + private void removeObsoleteEndpointsFromDns(LoadBalancers loadBalancers, DeploymentSpec deploymentSpec, @SuppressWarnings("unused") Lock lock) { var zonePolicies = get(loadBalancers.application, loadBalancers.zone); var removalCandidates = routingTableFrom(zonePolicies).keySet(); - var activeRoutingIds = routingIdsFrom(loadBalancers.list); + var activeRoutingIds = routingIdsFrom(loadBalancers, deploymentSpec); removalCandidates.removeAll(activeRoutingIds); for (var id : removalCandidates) { - Endpoint endpoint = RoutingPolicy.endpointOf(id.application(), id.rotation(), controller.system()); + Endpoint endpoint = RoutingPolicy.endpointOf(id.application(), id.endpointId(), controller.system()); controller.nameServiceForwarder().removeRecords(Record.Type.ALIAS, RecordName.from(endpoint.dnsName()), Priority.normal); } } /** Compute routing IDs from given load balancers */ - private static Set<RoutingId> routingIdsFrom(List<LoadBalancer> loadBalancers) { + private static Set<RoutingId> routingIdsFrom(LoadBalancers loadBalancers, DeploymentSpec spec) { Set<RoutingId> routingIds = new LinkedHashSet<>(); - for (var loadBalancer : loadBalancers) { - for (var rotation : loadBalancer.rotations()) { - routingIds.add(new RoutingId(loadBalancer.application(), rotation)); + for (var loadBalancer : loadBalancers.list) { + for (var endpointId : endpointIdsOf(loadBalancer, loadBalancers.zone, spec)) { + routingIds.add(new RoutingId(loadBalancer.application(), endpointId)); } } return Collections.unmodifiableSet(routingIds); @@ -171,7 +174,7 @@ public class RoutingPolicies { private static Map<RoutingId, List<RoutingPolicy>> routingTableFrom(Set<RoutingPolicy> routingPolicies) { var routingTable = new LinkedHashMap<RoutingId, List<RoutingPolicy>>(); for (var policy : routingPolicies) { - for (var rotation : policy.rotations()) { + for (var rotation : policy.endpoints()) { var id = new RoutingId(policy.owner(), rotation); routingTable.putIfAbsent(id, new ArrayList<>()); routingTable.get(id).add(policy); @@ -180,6 +183,16 @@ public class RoutingPolicies { return routingTable; } + /** Compute all endpoint IDs of given load balancer */ + private static Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer, ZoneId zone, DeploymentSpec spec) { + return spec.endpoints().stream() + .filter(endpoint -> endpoint.containerId().equals(loadBalancer.cluster().value())) + .filter(endpoint -> endpoint.regions().contains(zone.region())) + .map(com.yahoo.config.application.api.Endpoint::endpointId) + .map(EndpointId::of) + .collect(Collectors.toSet()); + } + /** Load balancers for a particular deployment */ private static class LoadBalancers { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java index 0c045eb7253..b9581d63583 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java @@ -14,7 +14,7 @@ import com.yahoo.slime.ObjectTraverser; import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; @@ -42,7 +42,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -110,6 +109,7 @@ public class ApplicationSerializer { private final String lastWrittenField = "lastWritten"; private final String lastQueriesPerSecondField = "lastQueriesPerSecond"; private final String lastWritesPerSecondField = "lastWritesPerSecond"; + private final String clusterMetricsField = "clusterMetrics"; // DeploymentJobs fields private final String projectIdField = "projectId"; @@ -555,27 +555,6 @@ public class ApplicationSerializer { private List<AssignedRotation> assignedRotationsFromSlime(DeploymentSpec deploymentSpec, Inspector root) { final var assignedRotations = new LinkedHashMap<EndpointId, AssignedRotation>(); - // Add the legacy rotation field to the set - this needs to be first - // TODO: Remove when we retire the rotations field - final var legacyRotation = legacyRotationFromSlime(root.field(deprecatedRotationField)); - if (legacyRotation.isPresent() && deploymentSpec.globalServiceId().isPresent()) { - final var clusterId = new ClusterSpec.Id(deploymentSpec.globalServiceId().get()); - final var regions = deploymentSpec.zones().stream().flatMap(zone -> zone.region().stream()).collect(Collectors.toSet()); - assignedRotations.putIfAbsent(EndpointId.default_(), new AssignedRotation(clusterId, EndpointId.default_(), legacyRotation.get(), regions)); - } - - // Now add the same entries from "stupid" list of rotations - // TODO: Remove when we retire the rotations field - final var rotations = rotationListFromSlime(root.field(rotationsField)); - for (var rotation : rotations) { - final var regions = deploymentSpec.zones().stream().flatMap(zone -> zone.region().stream()).collect(Collectors.toSet()); - if (deploymentSpec.globalServiceId().isPresent()) { - final var clusterId = new ClusterSpec.Id(deploymentSpec.globalServiceId().get()); - assignedRotations.putIfAbsent(EndpointId.default_(), new AssignedRotation(clusterId, EndpointId.default_(), rotation, regions)); - } - } - - // Last - add the actual entries we want. Do _not_ remove this during clean-up root.field(assignedRotationsField).traverse((ArrayTraverser) (idx, inspector) -> { final var clusterId = new ClusterSpec.Id(inspector.field(assignedRotationClusterField).asString()); final var endpointId = EndpointId.of(inspector.field(assignedRotationEndpointField).asString()); @@ -590,22 +569,6 @@ public class ApplicationSerializer { return List.copyOf(assignedRotations.values()); } - private List<RotationId> rotationListFromSlime(Inspector field) { - final var rotations = new ArrayList<RotationId>(); - - field.traverse((ArrayTraverser) (idx, inspector) -> { - final var rotation = new RotationId(inspector.asString()); - rotations.add(rotation); - }); - - return rotations; - } - - // TODO: Remove after June 2019 once the 'rotation' field is gone from storage - private Optional<RotationId> legacyRotationFromSlime(Inspector field) { - return field.valid() ? optionalString(field).map(RotationId::new) : Optional.empty(); - } - private OptionalLong optionalLong(Inspector field) { return field.valid() ? OptionalLong.of(field.asLong()) : OptionalLong.empty(); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java index d704d701cf0..b7b64b9cda2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java @@ -106,10 +106,6 @@ public class CuratorDb { CuratorDb(Curator curator, Duration tryLockTimeout) { this.curator = curator; this.tryLockTimeout = tryLockTimeout; - - // TODO: Remove after 7.60 - curator.delete(root.append("openStackServerPool")); - curator.delete(root.append("vespaServerPool")); } /** Returns all hosts configured to be part of this ZooKeeper cluster */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java index 9cfce8dc16a..80858e713c2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java @@ -4,11 +4,10 @@ package com.yahoo.vespa.hosted.controller.persistence; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.slime.ArrayTraverser; -import com.yahoo.slime.Cursor; import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.controller.application.EndpointId; import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import java.util.Collections; @@ -39,36 +38,36 @@ public class RoutingPolicySerializer { private static final String rotationsField = "rotations"; public Slime toSlime(Set<RoutingPolicy> routingPolicies) { - Slime slime = new Slime(); - Cursor root = slime.setObject(); - Cursor policyArray = root.setArray(routingPoliciesField); + var slime = new Slime(); + var root = slime.setObject(); + var policyArray = root.setArray(routingPoliciesField); routingPolicies.forEach(policy -> { - Cursor policyObject = policyArray.addObject(); + var policyObject = policyArray.addObject(); policyObject.setString(clusterField, policy.cluster().value()); policyObject.setString(zoneField, policy.zone().value()); policyObject.setString(canonicalNameField, policy.canonicalName().value()); policy.dnsZone().ifPresent(dnsZone -> policyObject.setString(dnsZoneField, dnsZone)); - Cursor rotationArray = policyObject.setArray(rotationsField); - policy.rotations().forEach(rotation -> { - rotationArray.addString(rotation.value()); + var rotationArray = policyObject.setArray(rotationsField); + policy.endpoints().forEach(endpointId -> { + rotationArray.addString(endpointId.id()); }); }); return slime; } public Set<RoutingPolicy> fromSlime(ApplicationId owner, Slime slime) { - Set<RoutingPolicy> policies = new LinkedHashSet<>(); - Cursor root = slime.get(); - Cursor field = root.field(routingPoliciesField); + var policies = new LinkedHashSet<RoutingPolicy>(); + var root = slime.get(); + var field = root.field(routingPoliciesField); field.traverse((ArrayTraverser) (i, inspect) -> { - Set<RotationName> rotations = new LinkedHashSet<>(); - inspect.field(rotationsField).traverse((ArrayTraverser) (j, rotation) -> rotations.add(RotationName.from(rotation.asString()))); + var endpointIds = new LinkedHashSet<EndpointId>(); + inspect.field(rotationsField).traverse((ArrayTraverser) (j, endpointId) -> endpointIds.add(EndpointId.of(endpointId.asString()))); policies.add(new RoutingPolicy(owner, ClusterSpec.Id.from(inspect.field(clusterField).asString()), ZoneId.from(inspect.field(zoneField).asString()), HostName.from(inspect.field(canonicalNameField).asString()), Serializers.optionalField(inspect.field(dnsZoneField), Function.identity()), - rotations)); + endpointIds)); }); return Collections.unmodifiableSet(policies); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java new file mode 100644 index 00000000000..31058a71816 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java @@ -0,0 +1,30 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.flags; + +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.vespa.configserver.flags.FlagsDb; +import com.yahoo.vespa.configserver.flags.http.FlagsHandler; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger; + +/** + * An extension of {@link FlagsHandler} which logs requests to the audit log. + * + * @author mpolden + */ +public class AuditedFlagsHandler extends FlagsHandler { + + private final AuditLogger auditLogger; + + public AuditedFlagsHandler(Context context, Controller controller, FlagsDb flagsDb) { + super(context, flagsDb); + auditLogger = controller.auditLogger(); + } + + @Override + public HttpResponse handle(HttpRequest request) { + return super.handle(auditLogger.log(request)); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java index ab5fd2714e5..82838c8de32 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java @@ -7,7 +7,6 @@ import com.yahoo.component.Version; import com.yahoo.component.Vtag; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.zone.ZoneApi; -import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; @@ -168,7 +167,7 @@ public class VersionStatus { boolean configConverged = application.configConvergedIn(zone.getId(), controller, Optional.empty()); if (!configConverged) { - log.log(LogLevel.WARNING, "Config for " + application.id() + " in " + zone + " has not converged"); + log.log(LogLevel.WARNING, "Config for " + application.id() + " in " + zone.getId() + " has not converged"); } for (Node node : eligibleForUpgradeApplicationNodes) { // Only use current node version if config has converged diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java index d5935c752d9..40936dd8a68 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java @@ -248,8 +248,7 @@ public final class ControllerTester { public AthenzDomain createDomainWithAdmin(String domainName, AthenzUser user) { AthenzDomain domain = new AthenzDomain(domainName); - athenzDb.addDomain(new AthenzDbMock.Domain(domain)); - athenzDb.domains.get(domain).admin(user); + athenzDb.getOrCreateDomain(domain).admin(user); return domain; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java index f5047a82e2f..bf798d2f004 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.Endpoint.Port; @@ -23,51 +22,51 @@ public class EndpointTest { @Test public void test_global_endpoints() { - RotationName rotation = RotationName.from("default"); // Always default for non-direct routing + EndpointId endpointId = EndpointId.default_(); Map<String, Endpoint> tests = Map.of( // Legacy endpoint "http://a1.t1.global.vespa.yahooapis.com:4080/", - Endpoint.of(app1).target(rotation).on(Port.plain(4080)).legacy().in(SystemName.main), + Endpoint.of(app1).named(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), // Legacy endpoint with TLS "https://a1--t1.global.vespa.yahooapis.com:4443/", - Endpoint.of(app1).target(rotation).on(Port.tls(4443)).legacy().in(SystemName.main), + Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), // Main endpoint "https://a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(rotation).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.main), // Main endpoint in CD "https://cd--a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(rotation).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint with direct routing and default TLS port "https://a1.t1.global.vespa.oath.cloud/", - Endpoint.of(app1).target(rotation).on(Port.tls()).directRouting().in(SystemName.main), + Endpoint.of(app1).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint with custom rotation name "https://r1.a1.t1.global.vespa.oath.cloud/", - Endpoint.of(app1).target(RotationName.from("r1")).on(Port.tls()).directRouting().in(SystemName.main), + Endpoint.of(app1).named(EndpointId.of("r1")).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint for custom instance in default rotation "https://a2.t2.global.vespa.oath.cloud/", - Endpoint.of(app2).target(rotation).on(Port.tls()).directRouting().in(SystemName.main), + Endpoint.of(app2).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint for custom instance with custom rotation name "https://r2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(app2).target(RotationName.from("r2")).on(Port.tls()).directRouting().in(SystemName.main), + Endpoint.of(app2).named(EndpointId.of("r2")).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint in public system "https://a1.t1.global.public.vespa.oath.cloud/", - Endpoint.of(app1).target(rotation).on(Port.tls()).directRouting().in(SystemName.Public) + Endpoint.of(app1).named(endpointId).on(Port.tls()).directRouting().in(SystemName.Public) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); } @Test public void test_global_endpoints_with_endpoint_id() { - final var endpointId = EndpointId.default_(); + var endpointId = EndpointId.default_(); Map<String, Endpoint> tests = Map.of( // Legacy endpoint @@ -111,9 +110,9 @@ public class EndpointTest { @Test public void test_zone_endpoints() { - ClusterSpec.Id cluster = ClusterSpec.Id.from("default"); // Always default for non-direct routing - ZoneId prodZone = ZoneId.from("prod", "us-north-1"); - ZoneId testZone = ZoneId.from("test", "us-north-2"); + var cluster = ClusterSpec.Id.from("default"); // Always default for non-direct routing + var prodZone = ZoneId.from("prod", "us-north-1"); + var testZone = ZoneId.from("test", "us-north-2"); Map<String, Endpoint> tests = Map.of( // Legacy endpoint (always contains environment) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java index a992ce1e3de..46e991a135a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java @@ -19,6 +19,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; +import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock; import com.yahoo.vespa.hosted.controller.integration.RoutingGeneratorMock; import com.yahoo.vespa.hosted.controller.maintenance.JobControl; @@ -34,12 +35,16 @@ import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted; import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; public class InternalDeploymentTester { + private static final String ATHENZ_DOMAIN = "domain"; + private static final String ATHENZ_SERVICE = "service"; + public static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() - .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service")) + .athenzIdentity(AthenzDomain.from(ATHENZ_DOMAIN), AthenzService.from(ATHENZ_SERVICE)) .upgradePolicy("default") .region("us-central-1") .parallel("us-west-1", "us-east-3") @@ -83,6 +88,10 @@ public class InternalDeploymentTester { Logger.getLogger(InternalStepRunner.class.getName()).setLevel(LogLevel.DEBUG); Logger.getLogger("").setLevel(LogLevel.DEBUG); tester.controllerTester().configureDefaultLogHandler(handler -> handler.setLevel(LogLevel.DEBUG)); + + // Mock Athenz domain to allow launch of service + AthenzDbMock.Domain domain = tester.controllerTester().athenzDb().getOrCreateDomain(new com.yahoo.vespa.athenz.api.AthenzDomain(ATHENZ_DOMAIN)); + domain.services.put(ATHENZ_SERVICE, new AthenzDbMock.Service(true)); } /** @@ -171,7 +180,7 @@ public class InternalDeploymentTester { .findAny() .orElseThrow(() -> new AssertionError(type + " is not among the active: " + jobs.active())); assertFalse(run.hasFailed()); - assertFalse(run.status() == aborted); + assertNotSame(aborted, run.status()); ZoneId zone = type.zone(tester.controller().system()); DeploymentId deployment = new DeploymentId(appId, zone); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java index 095651df033..f8157680cfd 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java @@ -3,15 +3,12 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.component.Version; import com.yahoo.config.application.api.DeploymentSpec; -import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Inspector; -import com.yahoo.slime.JsonFormat; -import com.yahoo.slime.ObjectTraverser; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.RefeedAction; @@ -28,7 +25,6 @@ import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import org.junit.Before; import org.junit.Test; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; @@ -38,7 +34,6 @@ import java.nio.file.Paths; import java.time.Duration; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; @@ -404,7 +399,7 @@ public class InternalStepRunnerTest { @Test public void generates_correct_services_xml_test() { - assertFile("test_runner_services.xml-cd", new String(InternalStepRunner.servicesXml(SystemName.cd, Optional.of("d-2-12-75")))); + assertFile("test_runner_services.xml-cd", new String(InternalStepRunner.servicesXml(AthenzDomain.from("vespa.vespa.cd"), Optional.of("d-2-12-75")))); } private void assertFile(String resourceName, String actualContent) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java index a89c5988396..fbc7bf20a24 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java @@ -11,6 +11,7 @@ import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus; import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions; +import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname; import com.yahoo.vespa.hosted.controller.api.identifiers.Identifier; @@ -330,6 +331,11 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer return applicationView; } + @Override + public List<ClusterMetrics> getMetrics(DeploymentId deployment) { + return List.of(); + } + // Returns a canned example response @Override public Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName, diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsServiceMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsServiceMock.java index ce2f74e3389..d925f7a33db 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsServiceMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsServiceMock.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.integration; import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostName; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService; import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.config.provision.zone.ZoneId; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java index f802a6af549..cff0f8da463 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java @@ -6,6 +6,7 @@ import com.google.inject.Inject; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; @@ -27,7 +28,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; /** * @author mpolden @@ -108,6 +108,11 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry } @Override + public AthenzDomain accessControlDomain() { + return AthenzDomain.from("vespadomain"); + } + + @Override public UpgradePolicy upgradePolicy() { return upgradePolicy; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java index 540efcba3b8..ad4511e7f11 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java @@ -1,10 +1,8 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.maintenance; -import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockResourceSnapshotConsumer; import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryClientMock; @@ -13,10 +11,7 @@ import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; import org.junit.Test; import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.Map; import static org.junit.Assert.*; @@ -41,20 +36,20 @@ public class ResourceMeterMaintainerTest { ResourceMeterMaintainer resourceMeterMaintainer = new ResourceMeterMaintainer(tester.controller(), Duration.ofMinutes(5), new JobControl(tester.curator()), nodeRepository, tester.clock(), metrics, snapshotConsumer); resourceMeterMaintainer.maintain(); - Map<ApplicationId, ResourceSnapshot> consumedResources = snapshotConsumer.consumedResources(); + List<ResourceSnapshot> consumedResources = snapshotConsumer.consumedResources(); + // The mocked repository contains two applications, so we should also consume two ResourceSnapshots assertEquals(2, consumedResources.size()); + ResourceSnapshot app1 = consumedResources.stream().filter(snapshot -> snapshot.getApplicationId().equals(ApplicationId.from("tenant1", "app1", "default"))).findFirst().orElseThrow(); + ResourceSnapshot app2 = consumedResources.stream().filter(snapshot -> snapshot.getApplicationId().equals(ApplicationId.from("tenant2", "app2", "default"))).findFirst().orElseThrow(); - ResourceSnapshot app1 = consumedResources.get(ApplicationId.from("tenant1", "app1", "default")); - ResourceSnapshot app2 = consumedResources.get(ApplicationId.from("tenant2", "app2", "default")); + assertEquals(24, app1.getCpuCores(), DELTA); + assertEquals(24, app1.getMemoryGb(), DELTA); + assertEquals(500, app1.getDiskGb(), DELTA); - assertEquals(24, app1.getResourceAllocation().getCpuCores(), DELTA); - assertEquals(24, app1.getResourceAllocation().getMemoryGb(), DELTA); - assertEquals(500, app1.getResourceAllocation().getDiskGb(), DELTA); - - assertEquals(40, app2.getResourceAllocation().getCpuCores(), DELTA); - assertEquals(24, app2.getResourceAllocation().getMemoryGb(), DELTA); - assertEquals(500, app2.getResourceAllocation().getDiskGb(), DELTA); + assertEquals(40, app2.getCpuCores(), DELTA); + assertEquals(24, app2.getMemoryGb(), DELTA); + assertEquals(500, app2.getDiskGb(), DELTA); assertEquals(tester.clock().millis()/1000, metrics.getMetric("metering_last_reported")); assertEquals(1112.0d, (Double) metrics.getMetric("metering_total_reported"), DELTA); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java index f0344cb8d12..e6387ee3e0c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java @@ -3,19 +3,18 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.RotationName; -import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer; import com.yahoo.vespa.hosted.controller.api.integration.dns.Record; +import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData; import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; +import com.yahoo.vespa.hosted.controller.deployment.BuildJob; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import org.junit.Test; @@ -26,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.Supplier; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -54,59 +52,88 @@ public class RoutingPoliciesTest { @Test public void maintains_global_routing_policies() { - int buildNumber = 42; + long buildNumber = BuildJob.defaultBuildNumber; int clustersPerZone = 2; - // Cluster 0 is member of 2 global rotations - Map<Integer, Set<RotationName>> rotations = Map.of(0, Set.of(RotationName.from("r0"), RotationName.from("r1"))); - provisionLoadBalancers(clustersPerZone, rotations, app1.id(), zone1, zone2); + int numberOfDeployments = 2; + var applicationPackage = new ApplicationPackageBuilder() + .region(zone1.region()) + .region(zone2.region()) + .endpoint("r0", "c0") + .endpoint("r1", "c0", "us-west-1") + .endpoint("r2", "c1") + .build(); + provisionLoadBalancers(clustersPerZone, app1.id(), zone1, zone2); - // Creates alias records for cluster0 + // Creates alias records tester.deployCompletely(app1, applicationPackage, ++buildNumber); - Supplier<List<Record>> records1 = () -> tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from("r0.app1.tenant1.global.vespa.oath.cloud")); - Supplier<List<Record>> records2 = () -> tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from("r1.app1.tenant1.global.vespa.oath.cloud")); - assertEquals(2, records1.get().size()); - assertEquals(records1.get().size(), records2.get().size()); - assertEquals("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records1.get().get(0).data().asString()); - assertEquals("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records1.get().get(1).data().asString()); - assertEquals("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records2.get().get(0).data().asString()); - assertEquals("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records2.get().get(1).data().asString()); - assertEquals(2, tester.controller().applications().routingPolicies().get(app1.id()).iterator().next() - .rotationEndpointsIn(SystemName.main).asList().size()); + var endpoint1 = "r0.app1.tenant1.global.vespa.oath.cloud"; + var endpoint2 = "r1.app1.tenant1.global.vespa.oath.cloud"; + var endpoint3 = "r2.app1.tenant1.global.vespa.oath.cloud"; + + assertEquals(endpoint1 + " points to c0 in all regions", + List.of("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", + "lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"), + aliasDataOf(endpoint1)); + assertEquals(endpoint2 + " points to c0 us-west-1", + List.of("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"), + aliasDataOf(endpoint2)); + assertEquals(endpoint3 + " points to c1 in all regions", + List.of("lb-1--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", + "lb-1--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"), + aliasDataOf(endpoint3)); + assertEquals("Routing policy count is equal to cluster count", + numberOfDeployments * clustersPerZone, + tester.controller().applications().routingPolicies().get(app1.id()).size()); // Applications gains a new deployment - ApplicationPackage updatedApplicationPackage = new ApplicationPackageBuilder() - .environment(Environment.prod) + ApplicationPackage applicationPackage2 = new ApplicationPackageBuilder() + .region(zone1.region()) + .region(zone2.region()) + .region(zone3.region()) + .endpoint("r0", "c0") + .endpoint("r1", "c0", "us-west-1") + .endpoint("r2", "c1") + .build(); + numberOfDeployments++; + provisionLoadBalancers(clustersPerZone, app1.id(), zone3); + tester.deployCompletely(app1, applicationPackage2, ++buildNumber); + + // Endpoint is updated to contain cluster in new deployment + assertEquals(endpoint1 + " points to c0 in all regions", + List.of("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", + "lb-0--tenant1:app1:default--prod.us-east-3/dns-zone-1/prod.us-east-3", + "lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"), + aliasDataOf(endpoint1)); + + // Another application is deployed with a single cluster and global endpoint + var endpoint4 = "r0.app2.tenant1.global.vespa.oath.cloud"; + provisionLoadBalancers(1, app2.id(), zone1, zone2); + var applicationPackage3 = new ApplicationPackageBuilder() + .region(zone1.region()) + .region(zone2.region()) + .endpoint("r0", "c0") + .build(); + tester.deployCompletely(app2, applicationPackage3); + assertEquals(endpoint4 + " points to c0 in all regions", + List.of("lb-0--tenant1:app2:default--prod.us-central-1/dns-zone-1/prod.us-central-1", + "lb-0--tenant1:app2:default--prod.us-west-1/dns-zone-1/prod.us-west-1"), + aliasDataOf(endpoint4)); + + // All endpoints for app1 are removed + ApplicationPackage applicationPackage4 = new ApplicationPackageBuilder() .region(zone1.region()) .region(zone2.region()) .region(zone3.region()) .build(); - int numberOfDeployments = 3; - provisionLoadBalancers(clustersPerZone, rotations, app1.id(), zone3); - tester.deployCompletely(app1, updatedApplicationPackage, ++buildNumber); - - // Cluster in new deployment is added to the rotation - assertEquals(numberOfDeployments, records1.get().size()); - assertEquals("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records1.get().get(0).data().asString()); - assertEquals("lb-0--tenant1:app1:default--prod.us-east-3/dns-zone-1/prod.us-east-3", records1.get().get(1).data().asString()); - assertEquals("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records1.get().get(2).data().asString()); - - // Another application is deployed - Supplier<List<Record>> records3 = () -> tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from("r0.app2.tenant1.global.vespa.oath.cloud")); - provisionLoadBalancers(1, Map.of(0, Set.of(RotationName.from("r0"))), app2.id(), zone1, zone2); - tester.deployCompletely(app2, applicationPackage); - assertEquals(2, records3.get().size()); - assertEquals("lb-0--tenant1:app2:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records3.get().get(0).data().asString()); - assertEquals("lb-0--tenant1:app2:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records3.get().get(1).data().asString()); - - // All rotations for app1 are removed - provisionLoadBalancers(clustersPerZone, Map.of(), app1.id(), zone1, zone2, zone3); - tester.deployCompletely(app1, updatedApplicationPackage, ++buildNumber); - assertEquals(List.of(), records1.get()); + tester.deployCompletely(app1, applicationPackage4, ++buildNumber); + assertEquals("DNS records are removed", List.of(), aliasDataOf(endpoint1)); + assertEquals("DNS records are removed", List.of(), aliasDataOf(endpoint2)); + assertEquals("DNS records are removed", List.of(), aliasDataOf(endpoint3)); Set<RoutingPolicy> policies = tester.controller().curator().readRoutingPolicies(app1.id()); assertEquals(clustersPerZone * numberOfDeployments, policies.size()); assertTrue("Rotation membership is removed from all policies", - policies.stream().allMatch(policy -> policy.rotations().isEmpty())); - assertEquals("Rotations for " + app2 + " are not removed", 2, records3.get().size()); + policies.stream().allMatch(policy -> policy.endpoints().isEmpty())); + assertEquals("Rotations for " + app2 + " are not removed", 2, aliasDataOf(endpoint4).size()); } @Test @@ -222,30 +249,30 @@ public class RoutingPoliciesTest { .collect(Collectors.toSet()); } - private void provisionLoadBalancers(int clustersPerZone, Map<Integer, Set<RotationName>> clusterRotations, ApplicationId application, ZoneId... zones) { - for (ZoneId zone : zones) { - tester.configServer().removeLoadBalancers(application, zone); - tester.configServer().addLoadBalancers(zone, createLoadBalancers(zone, application, clustersPerZone, clusterRotations)); - } + private List<String> aliasDataOf(String name) { + return tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from(name)).stream() + .map(Record::data) + .map(RecordData::asString) + .collect(Collectors.toList()); } private void provisionLoadBalancers(int clustersPerZone, ApplicationId application, ZoneId... zones) { - provisionLoadBalancers(clustersPerZone, Map.of(), application, zones); + for (ZoneId zone : zones) { + tester.configServer().removeLoadBalancers(application, zone); + tester.configServer().addLoadBalancers(zone, createLoadBalancers(zone, application, clustersPerZone)); + } } - private static List<LoadBalancer> createLoadBalancers(ZoneId zone, ApplicationId application, int count, - Map<Integer, Set<RotationName>> clusterRotations) { + private static List<LoadBalancer> createLoadBalancers(ZoneId zone, ApplicationId application, int count) { List<LoadBalancer> loadBalancers = new ArrayList<>(); for (int i = 0; i < count; i++) { - Set<RotationName> rotations = clusterRotations.getOrDefault(i, Collections.emptySet()); loadBalancers.add( new LoadBalancer("LB-" + i + "-Z-" + zone.value(), application, ClusterSpec.Id.from("c" + i), HostName.from("lb-" + i + "--" + application.serializedForm() + "--" + zone.value()), - Optional.of("dns-zone-1"), - rotations)); + Optional.of("dns-zone-1"))); } return loadBalancers; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java index 7b39b0d53a4..fb701746d77 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java @@ -10,7 +10,7 @@ import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.RegionName; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; @@ -253,9 +253,8 @@ public class ApplicationSerializerTest { // ok if no error } - /** TODO: Test can be removed after June 2019 - once legacy field for single rotation is retired */ @Test - public void testParsingLegacyRotationElement() throws IOException { + public void testParsingAssignedRotations() throws IOException { // Use the 'complete-application.json' as a baseline final var applicationJson = Files.readAllBytes(testData.resolve("complete-application.json")); final var slime = SlimeUtils.jsonToSlime(applicationJson); @@ -267,12 +266,6 @@ public class ApplicationSerializerTest { // Add the necessary fields to the Slime representation of the application final var cursor = slime.get(); - cursor.setString("rotation", "single-rotation"); - - final var rotations = cursor.setArray("endpoints"); - rotations.addString("multiple-rotation-1"); - rotations.addString("multiple-rotation-2"); - final var assignedRotations = cursor.setArray("assignedRotations"); final var assignedRotation = assignedRotations.addObject(); assignedRotation.setString("clusterId", "foobar"); @@ -282,51 +275,23 @@ public class ApplicationSerializerTest { // Parse and test the output from parsing contains both legacy rotation and multiple rotations final var application = applicationSerializer.fromSlime(slime); - // Since only one AssignedEndpoint can be "default", we make sure that we are ignoring the - // multiple-rotation entries as the globalServiceId will override them assertEquals( List.of( - new RotationId("single-rotation"), new RotationId("assigned-rotation") ), application.rotations() ); assertEquals( - Optional.of(new RotationId("single-rotation")), application.legacyRotation() + Optional.of(new RotationId("assigned-rotation")), application.legacyRotation() ); - // The same goes here for AssignedRotations with "default" EndpointId as in the .rotations() test above. - // Note that we are only using Set.of() on "assigned-rotation" because in this test we do not have access - // to a deployment.xml that describes the zones a rotation should map to. assertEquals( List.of( - new AssignedRotation(new ClusterSpec.Id("foo"), EndpointId.of("default"), new RotationId("single-rotation"), regions), new AssignedRotation(new ClusterSpec.Id("foobar"), EndpointId.of("nice-endpoint"), new RotationId("assigned-rotation"), Set.of()) ), application.assignedRotations() ); } - @Test - public void testParsingOnlyLegacyRotationElement() throws IOException { - // Use the 'complete-application.json' as a baseline - final var applicationJson = Files.readAllBytes(testData.resolve("complete-application.json")); - final var slime = SlimeUtils.jsonToSlime(applicationJson); - - // Add the necessary fields to the Slime representation of the application - final var cursor = slime.get(); - - cursor.setString("rotation", "single-rotation"); - - // Parse and test the output from parsing contains both legacy rotation and multiple rotations - final var application = applicationSerializer.fromSlime(slime); - - assertEquals( - List.of( - new RotationId("single-rotation") - ), - application.rotations() - ); - } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java index a0e95bd0393..e99cc302ffe 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java @@ -5,8 +5,8 @@ import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.application.EndpointId; import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import org.junit.Test; @@ -26,19 +26,19 @@ public class RoutingPolicySerializerTest { @Test public void test_serialization() { var owner = ApplicationId.defaultId(); - var rotations = Set.of(RotationName.from("r1"), RotationName.from("r2")); + var endpoints = Set.of(EndpointId.of("r1"), EndpointId.of("r2")); var policies = ImmutableSet.of(new RoutingPolicy(owner, ClusterSpec.Id.from("my-cluster1"), ZoneId.from("prod", "us-north-1"), HostName.from("long-and-ugly-name"), Optional.of("zone1"), - rotations), + endpoints), new RoutingPolicy(owner, ClusterSpec.Id.from("my-cluster2"), ZoneId.from("prod", "us-north-2"), HostName.from("long-and-ugly-name-2"), Optional.empty(), - rotations)); + endpoints)); var serialized = serializer.fromSlime(owner, serializer.toSlime(policies)); assertEquals(policies.size(), serialized.size()); for (Iterator<RoutingPolicy> it1 = policies.iterator(), it2 = serialized.iterator(); it1.hasNext();) { @@ -49,7 +49,7 @@ public class RoutingPolicySerializerTest { assertEquals(expected.zone(), actual.zone()); assertEquals(expected.canonicalName(), actual.canonicalName()); assertEquals(expected.dnsZone(), actual.dnsZone()); - assertEquals(expected.rotations(), actual.rotations()); + assertEquals(expected.endpoints(), actual.endpoints()); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java index 427428a3a94..54fe575a365 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java @@ -31,7 +31,6 @@ import com.yahoo.vespa.hosted.controller.integration.ArtifactRepositoryMock; import com.yahoo.vespa.hosted.controller.maintenance.JobControl; import com.yahoo.vespa.hosted.controller.maintenance.Upgrader; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; -import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import com.yahoo.vespa.hosted.controller.security.AthenzCredentials; import com.yahoo.vespa.hosted.controller.security.AthenzTenantSpec; @@ -159,10 +158,9 @@ public class ContainerControllerTester { AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components() .getComponent(AthenzClientFactoryMock.class.getName()); AthenzDomain athensDomain = new AthenzDomain(domainName); - AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain); + AthenzDbMock.Domain domain = mock.getSetup().getOrCreateDomain(athensDomain); domain.markAsVespaTenant(); domain.admin(new AthenzUser(userName)); - mock.getSetup().addDomain(domain); return athensDomain; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java index c7be543dd00..b32cbbcb926 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java @@ -7,7 +7,6 @@ import com.yahoo.application.container.handler.Response; import com.yahoo.component.ComponentSpecification; import com.yahoo.component.Version; import com.yahoo.config.provision.zone.ZoneApi; -import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.container.http.filter.FilterChainRepository; import com.yahoo.jdisc.http.filter.SecurityRequestFilter; import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 11aa132b478..83a43287880 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -60,6 +60,8 @@ public class ControllerContainerTest { " </rotations>\n" + " </config>\n" + " <component id='com.yahoo.vespa.flags.InMemoryFlagSource'/>\n" + + " <component id='com.yahoo.vespa.configserver.flags.db.FlagsDbImpl'/>\n" + + " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>\n" + @@ -112,6 +114,10 @@ public class ControllerContainerTest { " <binding>http://*/zone/v2</binding>\n" + " <binding>http://*/zone/v2/*</binding>\n" + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.flags.AuditedFlagsHandler'>\n" + + " <binding>http://*/flags/v1</binding>\n" + + " <binding>http://*/flags/v1/*</binding>\n" + + " </handler>\n" + variablePartXml() + "</container>"; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 577b8491bd2..8db820f6c83 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -11,7 +11,6 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.RotationName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.slime.Cursor; @@ -29,10 +28,10 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.api.integration.metrics.MetricsService.ApplicationMetrics; import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; @@ -44,6 +43,7 @@ import com.yahoo.vespa.hosted.controller.application.ClusterUtilization; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; +import com.yahoo.vespa.hosted.controller.application.EndpointId; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.RotationStatus; import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; @@ -562,6 +562,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN_2.getName()), AthenzService.from("service")) .region("us-west-1") .build(); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN_2, "service"), true); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST) .screwdriverIdentity(SCREWDRIVER_ID) .data(createApplicationSubmissionData(packageWithServiceForWrongDomain)), @@ -573,6 +574,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN.getName()), AthenzService.from("service")) .region("us-west-1") .build(); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST) .screwdriverIdentity(SCREWDRIVER_ID) .data(createApplicationSubmissionData(packageWithService)), @@ -1127,12 +1129,13 @@ public class ApplicationApiTest extends ControllerContainerTest { public void deployment_fails_on_illegal_domain_in_deployment_spec() { ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .upgradePolicy("default") - .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("invalid.domain"), com.yahoo.config.provision.AthenzService.from("service")) + .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("another.domain"), com.yahoo.config.provision.AthenzService.from("service")) .environment(Environment.prod) .region("us-west-1") .build(); long screwdriverProjectId = 123; createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(new AthenzDomain("another.domain"), "service"), true); Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default"); ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); @@ -1147,7 +1150,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, false)) .screwdriverIdentity(screwdriverId), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz domain in deployment.xml: [invalid.domain] must match tenant domain: [domain1]\"}", + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz domain in deployment.xml: [another.domain] must match tenant domain: [domain1]\"}", 400); } @@ -1164,6 +1167,7 @@ public class ApplicationApiTest extends ControllerContainerTest { ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true); Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default"); controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); @@ -1188,6 +1192,7 @@ public class ApplicationApiTest extends ControllerContainerTest { UserId tenantAdmin = new UserId("tenant-admin"); UserId userId = new UserId("new-user"); createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, tenantAdmin); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true); // Create tenant // PUT (create) the authenticated user @@ -1222,6 +1227,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.computeVersionStatus(); UserId tenantAdmin = new UserId("new_user"); createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, tenantAdmin); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true); // Create tenant // PUT (create) the authenticated user @@ -1247,6 +1253,39 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test + public void deployment_fails_when_athenz_service_cannot_be_launched() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .upgradePolicy("default") + .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service")) + .environment(Environment.prod) + .region("us-west-1") + .build(); + long screwdriverProjectId = 123; + ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); + + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), false); + + Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default"); + controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); + + // Allow systemtest to succeed by notifying completion of system test + controllerTester.jobCompletion(JobType.component) + .application(application.id()) + .projectId(screwdriverProjectId) + .uploadArtifact(applicationPackage) + .submit(); + + String expectedResult="{\"error-code\":\"BAD_REQUEST\",\"message\":\"Not allowed to launch Athenz service domain1.service\"}"; + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) + .data(createApplicationDeployData(applicationPackage, false)) + .screwdriverIdentity(screwdriverId), + expectedResult, + 400); + + } + + @Test public void redeployment_succeeds_when_not_specifying_versions_or_application_package() { // Setup addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); @@ -1262,6 +1301,7 @@ public class ApplicationApiTest extends ControllerContainerTest { ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true); Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default"); controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); @@ -1380,7 +1420,7 @@ public class ApplicationApiTest extends ControllerContainerTest { ClusterSpec.Id.from("default"), ZoneId.from(Environment.prod, RegionName.from("us-west-1")), HostName.from("lb-0-canonical-name"), - Optional.of("dns-zone-1"), Set.of(RotationName.from("c0"))); + Optional.of("dns-zone-1"), Set.of(EndpointId.of("c0"))); tester.controller().curator().writeRoutingPolicies(app.id(), Set.of(policy)); // GET application @@ -1476,13 +1516,23 @@ public class ApplicationApiTest extends ControllerContainerTest { private void createAthenzDomainWithAdmin(AthenzDomain domain, UserId userId) { AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() .getComponent(AthenzClientFactoryMock.class.getName()); - AthenzDbMock.Domain domainMock = new AthenzDbMock.Domain(domain); + AthenzDbMock.Domain domainMock = mock.getSetup().getOrCreateDomain(domain); domainMock.markAsVespaTenant(); domainMock.admin(AthenzUser.fromUserId(userId.id())); - mock.getSetup().addDomain(domainMock); } /** + * Mock athenz service identity configuration. Simulates that configserver is allowed to launch a service + */ + private void configureAthenzIdentity(com.yahoo.vespa.athenz.api.AthenzService service, boolean allowLaunch) { + AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() + .getComponent(AthenzClientFactoryMock.class.getName()); + AthenzDbMock.Domain domainMock = mock.getSetup().domains.computeIfAbsent(service.getDomain(), AthenzDbMock.Domain::new); + domainMock.services.put(service.getName(), new AthenzDbMock.Service(allowLaunch)); + } + + + /** * In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the * mock setup to replicate the action. */ diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java index 616db640132..614df953ca9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java @@ -2,11 +2,13 @@ package com.yahoo.vespa.hosted.controller.restapi.application; import com.yahoo.component.Version; +import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; -import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester; import org.json.JSONException; import org.json.JSONObject; @@ -134,12 +136,24 @@ public class JobControllerApiHandlerHelperTest { assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root")), "dev-overview.json"); } + @Test + public void testResponsesWithDirectDeployment() { + var tester = new InternalDeploymentTester(); + tester.clock().setInstant(Instant.EPOCH); + var region = "us-west-1"; + var applicationPackage = new ApplicationPackageBuilder().region(region).build(); + // Deploy directly to production zone, like integration tests. + tester.tester().controller().applications().deploy(tester.app().id(), ZoneId.from("prod", region), + Optional.of(applicationPackage), + new DeployOptions(true, Optional.empty(), + false, false)); + assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root/")), + "jobs-direct-deployment.json"); + } + private void compare(HttpResponse response, String expected) throws JSONException, IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); - - System.err.println(baos); - JSONObject actualJSON = new JSONObject(new String(baos.toByteArray())); JSONObject expectedJSON = new JSONObject(expected); assertEquals(expectedJSON.toString(), actualJSON.toString()); @@ -148,7 +162,7 @@ public class JobControllerApiHandlerHelperTest { private void assertResponse(HttpResponse response, String fileName) { try { Path path = Paths.get("src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/").resolve(fileName); - String expected = new String(Files.readAllBytes(path)); + String expected = Files.readString(path); compare(response, expected); } catch (Exception e) { throw new RuntimeException(e); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json new file mode 100644 index 00000000000..5535e286dcd --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json @@ -0,0 +1,79 @@ +{ + "devJobs": {}, + "deployments": [ + { + "us-west-1": { + "at": 0, + "application": { + "hash": "unknown" + }, + "verified": false, + "platform": "6.1" + } + } + ], + "lastVersions": {}, + "deploying": {}, + "jobs": { + "staging-test": { + "runs": [ + { + "reason": "Testing for productionUsWest1", + "wantedPlatform": "6.1", + "currentPlatform": "6.1", + "wantedApplication": { + "hash": "unknown" + }, + "currentApplication": { + "hash": "unknown" + }, + "tasks": { + "capacity": "running" + }, + "status": "pending" + } + ], + "url": "https://some.url:43/root/staging-test" + }, + "system-test": { + "runs": [ + { + "reason": "Testing for productionUsWest1", + "wantedPlatform": "6.1", + "currentPlatform": "6.1", + "wantedApplication": { + "hash": "unknown" + }, + "currentApplication": { + "hash": "unknown" + }, + "tasks": { + "capacity": "running" + }, + "status": "pending" + } + ], + "url": "https://some.url:43/root/system-test" + }, + "us-west-1": { + "runs": [ + { + "wantedPlatform": "6.1", + "currentPlatform": "6.1", + "wantedApplication": { + "hash": "unknown" + }, + "currentApplication": { + "hash": "unknown" + }, + "tasks": { + "staging-test": "pending", + "system-test": "pending" + }, + "status": "pending" + } + ], + "url": "https://some.url:43/root/production-us-west-1" + } + } +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java new file mode 100644 index 00000000000..b4ef98cc7f6 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java @@ -0,0 +1,57 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.flags; + +import com.yahoo.application.container.handler.Request; +import com.yahoo.vespa.athenz.api.AthenzIdentity; +import com.yahoo.vespa.athenz.api.AthenzUser; +import com.yahoo.vespa.hosted.controller.auditlog.AuditLog; +import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; +import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author mpolden + */ +public class AuditedFlagsApiTest extends ControllerContainerTest { + + private static final String responses = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/responses/"; + private static final AthenzIdentity operator = AthenzUser.fromUserId("operatorUser"); + + private ContainerControllerTester tester; + + @Before + public void before() { + addUserToHostedOperatorRole(operator); + tester = new ContainerControllerTester(container, responses); + } + + @Test + public void test_audit_logging() { + var body = "{\n" + + " \"id\": \"id1\",\n" + + " \"rules\": [\n" + + " {\n" + + " \"value\": true\n" + + " }\n" + + " ]\n" + + "}"; + assertResponse(new Request("http://localhost:8080/flags/v1/data/id1?force=true", body, Request.Method.PUT), + "", 200); + var log = tester.controller().auditLogger().readLog(); + assertEquals(1, log.entries().size()); + var entry = log.entries().get(0); + assertEquals(operator.getFullName(), entry.principal()); + assertEquals(AuditLog.Entry.Method.PUT, entry.method()); + assertEquals("/flags/v1/data/id1?force=true", entry.resource()); + assertEquals(body, log.entries().get(0).data().get()); + } + + private void assertResponse(Request request, String body, int statusCode) { + addIdentityToRequest(request, operator); + tester.assertResponse(request, body, statusCode); + } + +} diff --git a/defaults/src/main/java/com/yahoo/vespa/defaults/Defaults.java b/defaults/src/main/java/com/yahoo/vespa/defaults/Defaults.java index f1b7e38986f..6fb6e4f0860 100644 --- a/defaults/src/main/java/com/yahoo/vespa/defaults/Defaults.java +++ b/defaults/src/main/java/com/yahoo/vespa/defaults/Defaults.java @@ -114,11 +114,11 @@ public class Defaults { public String vespaHostname() { return vespaHost; } /** - * Returns the path where a Vespa application can store arbitrary files. This should only be used for temporary - * files as there are no availability guarantees for files stored here. The application must be able to recreate + * Returns the path where a Vespa application can store arbitrary files on the node. This path + * is persistent during the lifetime of this node. The application must be able to recreate * required files on its own (e.g. by downloading them from a remote source) if missing. * - * @return the temporary storage path + * @return the local application storage path */ public String temporaryApplicationStorage() { return temporaryApplicationStorage; } diff --git a/dist/vespa.spec b/dist/vespa.spec index 3426eff459d..ba1dbc32831 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -90,6 +90,7 @@ Requires: perl-Getopt-Long Requires: perl-IO-Socket-IP Requires: perl-JSON Requires: perl-libwww-perl +Requires: perl-LWP-Protocol-https Requires: perl-Net-INET6Glue Requires: perl-Pod-Usage Requires: perl-URI diff --git a/document/abi-spec.json b/document/abi-spec.json index 81cf5509a57..134200d96ec 100644 --- a/document/abi-spec.json +++ b/document/abi-spec.json @@ -3601,723 +3601,6 @@ ], "fields": [] }, - "com.yahoo.document.select.convert.NowQueryExpression": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.rule.AttributeNode, com.yahoo.document.select.rule.ComparisonNode, com.yahoo.document.select.rule.ArithmeticNode)", - "public java.lang.String getDocumentType()", - "public java.lang.String toString()" - ], - "fields": [] - }, - "com.yahoo.document.select.convert.NowQueryNode": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(long)", - "public void <init>(com.yahoo.document.select.rule.ArithmeticNode)", - "public java.lang.String toString()" - ], - "fields": [] - }, - "com.yahoo.document.select.convert.SelectionExpressionConverter": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.Visitor" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public java.util.Map getQueryMap()", - "public void visit(com.yahoo.document.select.rule.ArithmeticNode)", - "public void visit(com.yahoo.document.select.rule.AttributeNode)", - "public void visit(com.yahoo.document.select.rule.ComparisonNode)", - "public void visit(com.yahoo.document.select.rule.DocumentNode)", - "public void visit(com.yahoo.document.select.rule.EmbracedNode)", - "public void visit(com.yahoo.document.select.rule.IdNode)", - "public void visit(com.yahoo.document.select.rule.LiteralNode)", - "public void visit(com.yahoo.document.select.rule.LogicNode)", - "public void visit(com.yahoo.document.select.rule.NegationNode)", - "public void visit(com.yahoo.document.select.rule.NowNode)", - "public void visit(com.yahoo.document.select.rule.SearchColumnNode)", - "public void visit(com.yahoo.document.select.rule.VariableNode)" - ], - "fields": [] - }, - "com.yahoo.document.select.parser.CharStream": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public", - "interface", - "abstract" - ], - "methods": [ - "public abstract char readChar()", - "public abstract int getBeginColumn()", - "public abstract int getBeginLine()", - "public abstract int getEndColumn()", - "public abstract int getEndLine()", - "public abstract void backup(int)", - "public abstract char beginToken()", - "public abstract java.lang.String getImage()", - "public abstract char[] getSuffix(int)", - "public abstract void done()", - "public abstract void setTabSize(int)", - "public abstract int getTabSize()", - "public abstract void setTrackLineColumn(boolean)", - "public abstract boolean isTrackLineColumn()" - ], - "fields": [] - }, - "com.yahoo.document.select.parser.ParseException": { - "superClass": "java.lang.Exception", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.parser.Token, int[][], java.lang.String[])", - "public void <init>()", - "public void <init>(java.lang.String)" - ], - "fields": [ - "protected static final java.lang.String EOL", - "public com.yahoo.document.select.parser.Token currentToken", - "public int[][] expectedTokenSequences", - "public java.lang.String[] tokenImage" - ] - }, - "com.yahoo.document.select.parser.SelectInput": { - "superClass": "com.yahoo.javacc.FastCharStream", - "interfaces": [ - "com.yahoo.document.select.parser.CharStream" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(java.lang.String)" - ], - "fields": [] - }, - "com.yahoo.document.select.parser.SelectParser": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.parser.SelectParserConstants" - ], - "attributes": [ - "public" - ], - "methods": [ - "public final com.yahoo.document.select.rule.ExpressionNode expression()", - "public final com.yahoo.document.select.rule.ExpressionNode logic()", - "public final com.yahoo.document.select.rule.ExpressionNode negation()", - "public final com.yahoo.document.select.rule.NowNode now()", - "public final com.yahoo.document.select.rule.ExpressionNode relational()", - "public final com.yahoo.document.select.rule.ExpressionNode arithmetic()", - "public final com.yahoo.document.select.rule.VariableNode variable()", - "public final com.yahoo.document.select.rule.ExpressionNode attribute()", - "public final com.yahoo.document.select.rule.ExpressionNode value()", - "public final com.yahoo.document.select.rule.DocumentNode document()", - "public final void identifier()", - "public final com.yahoo.document.select.rule.IdNode id()", - "public final com.yahoo.document.select.rule.SearchColumnNode searchColumn()", - "public final com.yahoo.document.select.rule.LiteralNode literal()", - "public void <init>(com.yahoo.document.select.parser.CharStream)", - "public void ReInit(com.yahoo.document.select.parser.CharStream)", - "public void <init>(com.yahoo.document.select.parser.SelectParserTokenManager)", - "public void ReInit(com.yahoo.document.select.parser.SelectParserTokenManager)", - "public final com.yahoo.document.select.parser.Token getNextToken()", - "public final com.yahoo.document.select.parser.Token getToken(int)", - "public com.yahoo.document.select.parser.ParseException generateParseException()", - "public final boolean trace_enabled()", - "public final void enable_tracing()", - "public final void disable_tracing()" - ], - "fields": [ - "public com.yahoo.document.select.parser.SelectParserTokenManager token_source", - "public com.yahoo.document.select.parser.Token token", - "public com.yahoo.document.select.parser.Token jj_nt" - ] - }, - "com.yahoo.document.select.parser.SelectParserConstants": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public", - "interface", - "abstract" - ], - "methods": [], - "fields": [ - "public static final int EOF", - "public static final int INTEGER", - "public static final int DECIMAL", - "public static final int HEX", - "public static final int OCTAL", - "public static final int FLOAT", - "public static final int EXPONENT", - "public static final int LBRACE", - "public static final int RBRACE", - "public static final int ADD", - "public static final int SUB", - "public static final int MUL", - "public static final int DIV", - "public static final int MOD", - "public static final int DOT", - "public static final int COMMA", - "public static final int NE", - "public static final int GE", - "public static final int LE", - "public static final int REGEX", - "public static final int EQ", - "public static final int GLOB", - "public static final int GT", - "public static final int LT", - "public static final int DOLLAR", - "public static final int STRING", - "public static final int ID", - "public static final int SEARCHCOLUMN", - "public static final int ID_SCHEME", - "public static final int ID_TYPE", - "public static final int ID_NAMESPACE", - "public static final int ID_SPECIFIC", - "public static final int ID_USER", - "public static final int ID_GROUP", - "public static final int ID_ORDER", - "public static final int ID_BUCKET", - "public static final int NULL", - "public static final int NOW", - "public static final int TRUE", - "public static final int FALSE", - "public static final int AND", - "public static final int OR", - "public static final int NOT", - "public static final int IDENTIFIER", - "public static final int DEFAULT", - "public static final java.lang.String[] tokenImage" - ] - }, - "com.yahoo.document.select.parser.SelectParserTokenManager": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.parser.SelectParserConstants" - ], - "attributes": [ - "public" - ], - "methods": [ - "protected com.yahoo.document.select.parser.Token jjFillToken()", - "public com.yahoo.document.select.parser.Token getNextToken()", - "public void <init>(com.yahoo.document.select.parser.CharStream)", - "public void <init>(com.yahoo.document.select.parser.CharStream, int)", - "public void ReInit(com.yahoo.document.select.parser.CharStream)", - "public void ReInit(com.yahoo.document.select.parser.CharStream, int)", - "public void SwitchTo(int)" - ], - "fields": [ - "public static final java.lang.String[] jjstrLiteralImages", - "public static final java.lang.String[] lexStateNames", - "public static final int[] jjnewLexState", - "protected com.yahoo.document.select.parser.CharStream input_stream", - "protected int curChar" - ] - }, - "com.yahoo.document.select.parser.SelectParserUtils": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public static long decodeLong(java.lang.String)", - "public static java.lang.String quote(java.lang.String, char)", - "public static java.lang.String unquote(java.lang.String)" - ], - "fields": [] - }, - "com.yahoo.document.select.parser.Token": { - "superClass": "java.lang.Object", - "interfaces": [ - "java.io.Serializable" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public void <init>(int)", - "public void <init>(int, java.lang.String)", - "public java.lang.Object getValue()", - "public java.lang.String toString()", - "public static com.yahoo.document.select.parser.Token newToken(int, java.lang.String)", - "public static com.yahoo.document.select.parser.Token newToken(int)" - ], - "fields": [ - "public int kind", - "public int beginLine", - "public int beginColumn", - "public int endLine", - "public int endColumn", - "public java.lang.String image", - "public com.yahoo.document.select.parser.Token next", - "public com.yahoo.document.select.parser.Token specialToken" - ] - }, - "com.yahoo.document.select.parser.TokenMgrException": { - "superClass": "java.lang.RuntimeException", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "protected static final java.lang.String addEscapes(java.lang.String)", - "protected static java.lang.String LexicalErr(boolean, int, int, int, java.lang.String, int)", - "public java.lang.String getMessage()", - "public void <init>()", - "public void <init>(java.lang.String, int)", - "public void <init>(boolean, int, int, int, java.lang.String, int, int)" - ], - "fields": [ - "public static final int LEXICAL_ERROR", - "public static final int STATIC_LEXER_ERROR", - "public static final int INVALID_LEXICAL_STATE", - "public static final int LOOP_DETECTED" - ] - }, - "com.yahoo.document.select.rule.ArithmeticNode$NodeItem": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(int, com.yahoo.document.select.rule.ExpressionNode)", - "public int getOperator()", - "public com.yahoo.document.select.rule.ExpressionNode getNode()" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.ArithmeticNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public com.yahoo.document.select.rule.ArithmeticNode add(java.lang.String, com.yahoo.document.select.rule.ExpressionNode)", - "public java.util.List getItems()", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public java.lang.String toString()", - "public java.lang.String operatorToString(int)", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)", - "public void accept(com.yahoo.document.select.Visitor)" - ], - "fields": [ - "public static final int NOP", - "public static final int ADD", - "public static final int SUB", - "public static final int MOD", - "public static final int DIV", - "public static final int MUL" - ] - }, - "com.yahoo.document.select.rule.AttributeNode$Item": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(java.lang.String)", - "public java.lang.String getName()", - "public com.yahoo.document.select.rule.AttributeNode$Item setName(java.lang.String)", - "public int getType()", - "public com.yahoo.document.select.rule.AttributeNode$Item setType(int)", - "public java.lang.String toString()" - ], - "fields": [ - "public static final int ATTRIBUTE", - "public static final int FUNCTION" - ] - }, - "com.yahoo.document.select.rule.AttributeNode$VariableValueList": { - "superClass": "java.util.ArrayList", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.AttributeNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.rule.ExpressionNode, java.util.List)", - "public com.yahoo.document.select.rule.ExpressionNode getValue()", - "public com.yahoo.document.select.rule.AttributeNode setValue(com.yahoo.document.select.rule.ExpressionNode)", - "public java.util.List getItems()", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.ComparisonNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.rule.ExpressionNode, java.lang.String, com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.rule.ExpressionNode getLHS()", - "public com.yahoo.document.select.rule.ComparisonNode setLHS(com.yahoo.document.select.rule.ExpressionNode)", - "public java.lang.String getOperator()", - "public com.yahoo.document.select.rule.ComparisonNode setOperator(java.lang.String)", - "public com.yahoo.document.select.rule.ExpressionNode getRHS()", - "public com.yahoo.document.select.rule.ComparisonNode setRHS(com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.OrderingSpecification getOrdering(com.yahoo.document.select.rule.IdNode, com.yahoo.document.select.rule.LiteralNode, java.lang.String, int)", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public com.yahoo.document.select.ResultList evaluateListsTrue(com.yahoo.document.select.rule.AttributeNode$VariableValueList, com.yahoo.document.select.rule.AttributeNode$VariableValueList)", - "public com.yahoo.document.select.ResultList evaluateListsFalse(com.yahoo.document.select.rule.AttributeNode$VariableValueList, com.yahoo.document.select.rule.AttributeNode$VariableValueList)", - "public com.yahoo.document.select.ResultList evaluateListAndSingle(com.yahoo.document.select.rule.AttributeNode$VariableValueList, java.lang.Object)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.DocumentNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(java.lang.String)", - "public java.lang.String getType()", - "public com.yahoo.document.select.rule.DocumentNode setType(java.lang.String)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public java.lang.Object evaluate(com.yahoo.document.DocumentOperation)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.EmbracedNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.rule.ExpressionNode getNode()", - "public com.yahoo.document.select.rule.EmbracedNode setNode(com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public java.lang.String toString()", - "public void accept(com.yahoo.document.select.Visitor)", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.ExpressionNode": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public", - "interface", - "abstract" - ], - "methods": [ - "public abstract java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public abstract com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public abstract com.yahoo.document.select.OrderingSpecification getOrdering(int)", - "public abstract void accept(com.yahoo.document.select.Visitor)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.IdNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public java.lang.String getField()", - "public com.yahoo.document.select.rule.IdNode setField(java.lang.String)", - "public com.yahoo.document.select.rule.IdNode setWidthBits(short)", - "public com.yahoo.document.select.rule.IdNode setDivisionBits(short)", - "public short getWidthBits()", - "public short getDivisionBits()", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.LiteralNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(java.lang.Object)", - "public java.lang.Object getValue()", - "public com.yahoo.document.select.rule.LiteralNode setValue(java.lang.Object)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.LogicNode$NodeItem": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public", - "final" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.rule.LogicNode, int, com.yahoo.document.select.rule.ExpressionNode)", - "public int getOperator()", - "public com.yahoo.document.select.rule.ExpressionNode getNode()" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.LogicNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public java.util.List getItems()", - "public com.yahoo.document.select.rule.LogicNode add(java.lang.String, com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public java.lang.String operatorToString(int)" - ], - "fields": [ - "public static final int NOP", - "public static final int OR", - "public static final int AND" - ] - }, - "com.yahoo.document.select.rule.NegationNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.rule.ExpressionNode getNode()", - "public com.yahoo.document.select.rule.NegationNode setNode(com.yahoo.document.select.rule.ExpressionNode)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.NowNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)", - "public void accept(com.yahoo.document.select.Visitor)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.SearchColumnNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public int getField()", - "public com.yahoo.document.select.rule.SearchColumnNode setField(int)", - "public com.yahoo.document.BucketDistribution getDistribution()", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.rule.VariableNode": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.document.select.rule.ExpressionNode" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>(java.lang.String)", - "public java.lang.Object getValue()", - "public com.yahoo.document.select.rule.VariableNode setValue(java.lang.String)", - "public com.yahoo.document.select.BucketSet getBucketSet(com.yahoo.document.BucketIdFactory)", - "public java.lang.Object evaluate(com.yahoo.document.select.Context)", - "public void accept(com.yahoo.document.select.Visitor)", - "public java.lang.String toString()", - "public com.yahoo.document.select.OrderingSpecification getOrdering(int)" - ], - "fields": [] - }, - "com.yahoo.document.select.simple.IdSpecParser": { - "superClass": "com.yahoo.document.select.simple.Parser", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public com.yahoo.document.select.rule.IdNode getId()", - "public boolean isUserSpec()", - "public boolean parse(java.lang.CharSequence)" - ], - "fields": [] - }, - "com.yahoo.document.select.simple.IntegerParser": { - "superClass": "com.yahoo.document.select.simple.Parser", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public com.yahoo.document.select.rule.LiteralNode getValue()", - "public boolean parse(java.lang.CharSequence)" - ], - "fields": [] - }, - "com.yahoo.document.select.simple.OperatorParser": { - "superClass": "com.yahoo.document.select.simple.Parser", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public java.lang.String getOperator()", - "public boolean parse(java.lang.CharSequence)" - ], - "fields": [] - }, - "com.yahoo.document.select.simple.Parser": { - "superClass": "java.lang.Object", - "interfaces": [], - "attributes": [ - "public", - "abstract" - ], - "methods": [ - "public void <init>()", - "public abstract boolean parse(java.lang.CharSequence)", - "public java.lang.CharSequence getRemaining()", - "protected void setRemaining(java.lang.CharSequence)", - "protected int eatWhite(java.lang.CharSequence)", - "protected boolean icmp(char, char)" - ], - "fields": [] - }, - "com.yahoo.document.select.simple.SelectionParser": { - "superClass": "com.yahoo.document.select.simple.Parser", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public com.yahoo.document.select.rule.ExpressionNode getNode()", - "public boolean parse(java.lang.CharSequence)" - ], - "fields": [] - }, - "com.yahoo.document.select.simple.StringParser": { - "superClass": "com.yahoo.document.select.simple.Parser", - "interfaces": [], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public com.yahoo.document.select.rule.LiteralNode getValue()", - "public boolean parse(java.lang.CharSequence)" - ], - "fields": [] - }, "com.yahoo.document.serialization.AnnotationReader": { "superClass": "java.lang.Object", "interfaces": [], diff --git a/document/src/main/java/com/yahoo/document/select/Context.java b/document/src/main/java/com/yahoo/document/select/Context.java index ac2bb6b9f0d..481131b4776 100644 --- a/document/src/main/java/com/yahoo/document/select/Context.java +++ b/document/src/main/java/com/yahoo/document/select/Context.java @@ -7,7 +7,8 @@ import java.util.HashMap; import java.util.Map; public class Context { - private DocumentOperation documentOperation = null; + private DocumentOperation documentOperation; + private Map<String, Object> variables = new HashMap<>(); public Context(DocumentOperation documentOperation) { this.documentOperation = documentOperation; @@ -28,7 +29,4 @@ public class Context { public void setVariables(Map<String, Object> variables) { this.variables = variables; } - - private Map<String, Object> variables = new HashMap<String, Object>(); - } diff --git a/document/src/main/java/com/yahoo/document/select/DocumentSelector.java b/document/src/main/java/com/yahoo/document/select/DocumentSelector.java index 7f5b92ea233..1ee6ff45c99 100644 --- a/document/src/main/java/com/yahoo/document/select/DocumentSelector.java +++ b/document/src/main/java/com/yahoo/document/select/DocumentSelector.java @@ -84,7 +84,7 @@ public class DocumentSelector { * @return True if the document is accepted. * @throws RuntimeException if the evaluation enters an illegal state */ - public ResultList getMatchingResultList(Context context) { + private ResultList getMatchingResultList(Context context) { return ResultList.toResultList(expression.evaluate(context)); } diff --git a/document/src/main/java/com/yahoo/document/select/NowCheckVisitor.java b/document/src/main/java/com/yahoo/document/select/NowCheckVisitor.java index 5efcdec848a..fd799e36a70 100644 --- a/document/src/main/java/com/yahoo/document/select/NowCheckVisitor.java +++ b/document/src/main/java/com/yahoo/document/select/NowCheckVisitor.java @@ -1,8 +1,18 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.document.select; -import com.yahoo.document.select.Visitor; -import com.yahoo.document.select.rule.*; +import com.yahoo.document.select.rule.ArithmeticNode; +import com.yahoo.document.select.rule.AttributeNode; +import com.yahoo.document.select.rule.ComparisonNode; +import com.yahoo.document.select.rule.DocumentNode; +import com.yahoo.document.select.rule.EmbracedNode; +import com.yahoo.document.select.rule.IdNode; +import com.yahoo.document.select.rule.LiteralNode; +import com.yahoo.document.select.rule.LogicNode; +import com.yahoo.document.select.rule.NegationNode; +import com.yahoo.document.select.rule.NowNode; +import com.yahoo.document.select.rule.SearchColumnNode; +import com.yahoo.document.select.rule.VariableNode; /** * Traverse and check if there exists any now() function in the expression tree. diff --git a/document/src/main/java/com/yahoo/document/select/Result.java b/document/src/main/java/com/yahoo/document/select/Result.java index 784b7fdc7e7..5f057da900c 100644 --- a/document/src/main/java/com/yahoo/document/select/Result.java +++ b/document/src/main/java/com/yahoo/document/select/Result.java @@ -15,7 +15,7 @@ public enum Result { FALSE, INVALID; - // Inherit doc from Object. + @Override public String toString() { return name().toLowerCase(); } @@ -38,7 +38,7 @@ public enum Result { */ public static Result toResult(Object value) { if (value == null || value == Result.FALSE || value == Boolean.FALSE || - (Number.class.isInstance(value) && ((Number)value).doubleValue() == 0)) { + (value instanceof Number && ((Number)value).doubleValue() == 0)) { return Result.FALSE; } else if (value == INVALID) { return Result.INVALID; diff --git a/document/src/main/java/com/yahoo/document/select/ResultList.java b/document/src/main/java/com/yahoo/document/select/ResultList.java index 8d73c475981..edf05eb044e 100644 --- a/document/src/main/java/com/yahoo/document/select/ResultList.java +++ b/document/src/main/java/com/yahoo/document/select/ResultList.java @@ -43,7 +43,7 @@ public class ResultList { } } - List<ResultPair> results = new ArrayList<ResultPair>(); + private List<ResultPair> results = new ArrayList<>(); public ResultList() { } @@ -86,7 +86,7 @@ public class ResultList { } } - boolean combineVariables(FieldPathIteratorHandler.VariableMap output, FieldPathIteratorHandler.VariableMap input) { + private boolean combineVariables(FieldPathIteratorHandler.VariableMap output, FieldPathIteratorHandler.VariableMap input) { // First, verify that all variables are overlapping for (Map.Entry<String, FieldPathIteratorHandler.IndexValue> entry : output.entrySet()) { FieldPathIteratorHandler.IndexValue found = input.get(entry.getKey()); @@ -116,13 +116,19 @@ public class ResultList { return true; } - public ResultList combineAND(ResultList other) + public interface LazyResultList { + ResultList getResult(); + } + + public ResultList combineAND(LazyResultList other) { + if (Result.FALSE == toResult()) return ResultList.toResultList(false); + ResultList result = new ResultList(); // TODO: optimize for (ResultPair pair : results) { - for (ResultPair otherPair : other.results) { + for (ResultPair otherPair : other.getResult().results) { FieldPathIteratorHandler.VariableMap varMap = (FieldPathIteratorHandler.VariableMap)pair.variables.clone(); if (combineVariables(varMap, otherPair.variables)) { @@ -144,13 +150,15 @@ public class ResultList { return Result.INVALID; } - public ResultList combineOR(ResultList other) + public ResultList combineOR(LazyResultList other) { + if (Result.TRUE == toResult()) return ResultList.toResultList(true); + ResultList result = new ResultList(); // TODO: optimize for (ResultPair pair : results) { - for (ResultPair otherPair : other.results) { + for (ResultPair otherPair : other.getResult().results) { FieldPathIteratorHandler.VariableMap varMap = (FieldPathIteratorHandler.VariableMap)pair.variables.clone(); if (combineVariables(varMap, otherPair.variables)) { @@ -188,7 +196,7 @@ public class ResultList { } return retVal; } else if (value == null || value == Result.FALSE || value == Boolean.FALSE || - (Number.class.isInstance(value) && ((Number)value).doubleValue() == 0)) { + (value instanceof Number && ((Number)value).doubleValue() == 0)) { return new ResultList(Result.FALSE); } else if (value == Result.INVALID) { return new ResultList(Result.INVALID); diff --git a/document/src/main/java/com/yahoo/document/select/convert/NowQueryExpression.java b/document/src/main/java/com/yahoo/document/select/convert/NowQueryExpression.java index d379a73aec1..a4ce6afb099 100644 --- a/document/src/main/java/com/yahoo/document/select/convert/NowQueryExpression.java +++ b/document/src/main/java/com/yahoo/document/select/convert/NowQueryExpression.java @@ -16,7 +16,7 @@ public class NowQueryExpression { private final ComparisonNode comparison; private final NowQueryNode now; - public NowQueryExpression(AttributeNode attribute, ComparisonNode comparison, ArithmeticNode arithmetic) { + NowQueryExpression(AttributeNode attribute, ComparisonNode comparison, ArithmeticNode arithmetic) { this.attribute = attribute; this.comparison = comparison; this.now = (arithmetic != null ? new NowQueryNode(arithmetic) : new NowQueryNode(0)); diff --git a/document/src/main/java/com/yahoo/document/select/convert/NowQueryNode.java b/document/src/main/java/com/yahoo/document/select/convert/NowQueryNode.java index 121fa0a45eb..0bd94a3854c 100644 --- a/document/src/main/java/com/yahoo/document/select/convert/NowQueryNode.java +++ b/document/src/main/java/com/yahoo/document/select/convert/NowQueryNode.java @@ -10,10 +10,10 @@ import com.yahoo.document.select.rule.ArithmeticNode; */ public class NowQueryNode { private final long value; - public NowQueryNode(long value) { + NowQueryNode(long value) { this.value = value; } - public NowQueryNode(ArithmeticNode node) { + NowQueryNode(ArithmeticNode node) { // Assumes that the structure is checked and verified earlier this.value = Long.parseLong(node.getItems().get(1).getNode().toString()); } diff --git a/document/src/main/java/com/yahoo/document/select/convert/SelectionExpressionConverter.java b/document/src/main/java/com/yahoo/document/select/convert/SelectionExpressionConverter.java index 46721254b9f..a5708871529 100644 --- a/document/src/main/java/com/yahoo/document/select/convert/SelectionExpressionConverter.java +++ b/document/src/main/java/com/yahoo/document/select/convert/SelectionExpressionConverter.java @@ -3,7 +3,20 @@ package com.yahoo.document.select.convert; import com.yahoo.document.select.NowCheckVisitor; import com.yahoo.document.select.Visitor; -import com.yahoo.document.select.rule.*; +import com.yahoo.document.select.rule.ArithmeticNode; +import com.yahoo.document.select.rule.AttributeNode; +import com.yahoo.document.select.rule.ComparisonNode; +import com.yahoo.document.select.rule.DocumentNode; +import com.yahoo.document.select.rule.EmbracedNode; +import com.yahoo.document.select.rule.ExpressionNode; +import com.yahoo.document.select.rule.IdNode; +import com.yahoo.document.select.rule.LiteralNode; +import com.yahoo.document.select.rule.LogicNode; +import com.yahoo.document.select.rule.NegationNode; +import com.yahoo.document.select.rule.NowNode; +import com.yahoo.document.select.rule.SearchColumnNode; +import com.yahoo.document.select.rule.VariableNode; + import java.util.HashMap; import java.util.Map; @@ -15,14 +28,13 @@ import java.util.Map; */ public class SelectionExpressionConverter implements Visitor { - private Map<String, NowQueryExpression> expressionMap = new HashMap<String, NowQueryExpression>(); + private Map<String, NowQueryExpression> expressionMap = new HashMap<>(); private class BuildState { public AttributeNode attribute; public ComparisonNode comparison; public ArithmeticNode arithmetic; public NowNode now; - public boolean hasNow() { return now != null; } } private BuildState state; @@ -38,7 +50,7 @@ public class SelectionExpressionConverter implements Visitor { } public Map<String, String> getQueryMap() { - Map<String, String> ret = new HashMap<String, String>(); + Map<String, String> ret = new HashMap<>(); for (NowQueryExpression expression : expressionMap.values()) { ret.put(expression.getDocumentType(), expression.toString()); } @@ -92,7 +104,6 @@ public class SelectionExpressionConverter implements Visitor { } state.comparison = node; if (state.attribute != null && - state.comparison != null && (state.arithmetic != null || state.now != null)) { NowQueryExpression expression = new NowQueryExpression(state.attribute, state.comparison, state.arithmetic); expressionMap.put(expression.getDocumentType(), expression); diff --git a/document/src/main/java/com/yahoo/document/select/convert/package-info.java b/document/src/main/java/com/yahoo/document/select/convert/package-info.java index a3cd3c7c55b..aa7276099b0 100644 --- a/document/src/main/java/com/yahoo/document/select/convert/package-info.java +++ b/document/src/main/java/com/yahoo/document/select/convert/package-info.java @@ -1,7 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. @ExportPackage -@PublicApi package com.yahoo.document.select.convert; -import com.yahoo.api.annotations.PublicApi; import com.yahoo.osgi.annotation.ExportPackage; diff --git a/document/src/main/java/com/yahoo/document/select/parser/SelectParserUtils.java b/document/src/main/java/com/yahoo/document/select/parser/SelectParserUtils.java index 6537eefaa7a..2bfed803ea7 100644 --- a/document/src/main/java/com/yahoo/document/select/parser/SelectParserUtils.java +++ b/document/src/main/java/com/yahoo/document/select/parser/SelectParserUtils.java @@ -10,7 +10,7 @@ import java.math.BigInteger; */ public class SelectParserUtils { - public static long decodeLong(String str) { + static long decodeLong(String str) { if (str.startsWith("0x") || str.startsWith("0X")) { str = Long.toString(new BigInteger(str.substring(2), 16).longValue()); } diff --git a/document/src/main/java/com/yahoo/document/select/parser/package-info.java b/document/src/main/java/com/yahoo/document/select/parser/package-info.java index 11cda77e3e4..476f0409c6f 100644 --- a/document/src/main/java/com/yahoo/document/select/parser/package-info.java +++ b/document/src/main/java/com/yahoo/document/select/parser/package-info.java @@ -1,7 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. @ExportPackage -@PublicApi package com.yahoo.document.select.parser; -import com.yahoo.api.annotations.PublicApi; import com.yahoo.osgi.annotation.ExportPackage; diff --git a/document/src/main/java/com/yahoo/document/select/rule/ArithmeticNode.java b/document/src/main/java/com/yahoo/document/select/rule/ArithmeticNode.java index 813c3b27612..c39bd7e668d 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/ArithmeticNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/ArithmeticNode.java @@ -3,7 +3,10 @@ package com.yahoo.document.select.rule; import com.yahoo.document.BucketIdFactory; import com.yahoo.document.datatypes.NumericFieldValue; -import com.yahoo.document.select.*; +import com.yahoo.document.select.BucketSet; +import com.yahoo.document.select.Context; +import com.yahoo.document.select.OrderingSpecification; +import com.yahoo.document.select.Visitor; import java.util.List; import java.util.ArrayList; @@ -21,7 +24,7 @@ public class ArithmeticNode implements ExpressionNode { public static final int DIV = 4; public static final int MUL = 5; - private final List<NodeItem> items = new ArrayList<NodeItem>(); + private final List<NodeItem> items = new ArrayList<>(); public ArithmeticNode() { // empty @@ -36,15 +39,15 @@ public class ArithmeticNode implements ExpressionNode { return items; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return null; } - // Inherit doc from ExpressionNode. + @Override public Object evaluate(Context context) { StringBuilder ret = null; - Stack<ValueItem> buf = new Stack<ValueItem>(); + Stack<ValueItem> buf = new Stack<>(); for (int i = 0; i < items.size(); ++i) { NodeItem item = items.get(i); Object val = item.node.evaluate(context); @@ -77,7 +80,7 @@ public class ArithmeticNode implements ExpressionNode { ret.append(val); continue; } - } else if (Number.class.isInstance(val)) { + } else if (val instanceof Number) { if (!buf.isEmpty()) { while (buf.peek().operator > item.operator) { popOffTheTop(buf); @@ -171,10 +174,12 @@ public class ArithmeticNode implements ExpressionNode { } } + @Override public OrderingSpecification getOrdering(int order) { return null; } + @Override public void accept(Visitor visitor) { visitor.visit(this); } @@ -183,7 +188,7 @@ public class ArithmeticNode implements ExpressionNode { public int operator; public Number value; - public ValueItem(int operator, Number value) { + ValueItem(int operator, Number value) { this.operator = operator; this.value = value; } @@ -193,7 +198,7 @@ public class ArithmeticNode implements ExpressionNode { private int operator; private ExpressionNode node; - public NodeItem(int operator, ExpressionNode node) { + NodeItem(int operator, ExpressionNode node) { this.operator = operator; this.node = node; } diff --git a/document/src/main/java/com/yahoo/document/select/rule/AttributeNode.java b/document/src/main/java/com/yahoo/document/select/rule/AttributeNode.java index b6d75acd0ea..3e03709d0f1 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/AttributeNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/AttributeNode.java @@ -2,10 +2,21 @@ package com.yahoo.document.select.rule; import com.yahoo.collections.BobHash; -import com.yahoo.document.*; +import com.yahoo.document.BucketIdFactory; +import com.yahoo.document.Document; +import com.yahoo.document.DocumentGet; +import com.yahoo.document.DocumentPut; +import com.yahoo.document.DocumentRemove; +import com.yahoo.document.DocumentUpdate; +import com.yahoo.document.FieldPath; import com.yahoo.document.datatypes.FieldPathIteratorHandler; import com.yahoo.document.datatypes.FieldValue; -import com.yahoo.document.select.*; +import com.yahoo.document.select.BucketSet; +import com.yahoo.document.select.Context; +import com.yahoo.document.select.OrderingSpecification; +import com.yahoo.document.select.Result; +import com.yahoo.document.select.ResultList; +import com.yahoo.document.select.Visitor; import java.util.ArrayList; import java.util.List; @@ -16,7 +27,7 @@ import java.util.List; public class AttributeNode implements ExpressionNode { private ExpressionNode value; - private final List<Item> items = new ArrayList<Item>(); + private final List<Item> items = new ArrayList<>(); public AttributeNode(ExpressionNode value, List items) { this.value = value; @@ -43,14 +54,14 @@ public class AttributeNode implements ExpressionNode { return items; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return null; } - // Inherit doc from ExpressionNode. + @Override public Object evaluate(Context context) { - String pos = value.toString(); + StringBuilder pos = new StringBuilder(value.toString()); Object obj = value.evaluate(context); StringBuilder builder = new StringBuilder(); @@ -74,7 +85,7 @@ public class AttributeNode implements ExpressionNode { obj = evaluateFunction(item.getName(), obj); } - pos = pos + "." + item; + pos.append(".").append(item); } if (builder.length() > 0) { @@ -98,7 +109,7 @@ public class AttributeNode implements ExpressionNode { private static Object applyFunction(String function, Object value) { if (function.equalsIgnoreCase("abs")) { - if (Number.class.isInstance(value)) { + if (value instanceof Number) { Number nValue = (Number)value; if (value instanceof Double) { return nValue.doubleValue() * (nValue.doubleValue() < 0 ? -1 : 1); @@ -157,6 +168,7 @@ public class AttributeNode implements ExpressionNode { return applyFunction(function, value); } + @Override public void accept(Visitor visitor) { visitor.visit(this); } @@ -171,6 +183,7 @@ public class AttributeNode implements ExpressionNode { return ret.toString(); } + @Override public OrderingSpecification getOrdering(int order) { return null; } diff --git a/document/src/main/java/com/yahoo/document/select/rule/ComparisonNode.java b/document/src/main/java/com/yahoo/document/select/rule/ComparisonNode.java index 13a990566e3..a7ab3d62c58 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/ComparisonNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/ComparisonNode.java @@ -7,9 +7,13 @@ import com.yahoo.document.DocumentId; import com.yahoo.document.datatypes.FieldPathIteratorHandler; import com.yahoo.document.datatypes.NumericFieldValue; import com.yahoo.document.idstring.GroupDocIdString; -import com.yahoo.document.select.*; +import com.yahoo.document.select.BucketSet; +import com.yahoo.document.select.Context; +import com.yahoo.document.select.OrderingSpecification; +import com.yahoo.document.select.Result; +import com.yahoo.document.select.ResultList; +import com.yahoo.document.select.Visitor; -import java.util.List; import java.util.regex.Pattern; /** @@ -46,17 +50,6 @@ public class ComparisonNode implements ExpressionNode { } /** - * Sets the left hand side of this comparison. - * - * @param lhs The new left hand side. - * @return This, to allow chaining. - */ - public ComparisonNode setLHS(ExpressionNode lhs) { - this.lhs = lhs; - return this; - } - - /** * Returns the comparison operator of this. * * @return The operator. @@ -85,17 +78,6 @@ public class ComparisonNode implements ExpressionNode { return rhs; } - /** - * Sets the right hand side of this comparison. - * - * @param rhs The new right hand side. - * @return This, to allow chaining. - */ - public ComparisonNode setRHS(ExpressionNode rhs) { - this.rhs = rhs; - return this; - } - public OrderingSpecification getOrdering(IdNode lhs, LiteralNode rhs, String operator, int order) { if (lhs.getWidthBits() == -1 || lhs.getDivisionBits() == -1 || !(rhs.getValue() instanceof Long)) { return null; @@ -126,17 +108,18 @@ public class ComparisonNode implements ExpressionNode { return null; } + @Override public OrderingSpecification getOrdering(int order) { if (lhs instanceof IdNode && rhs instanceof LiteralNode) { return getOrdering((IdNode)lhs, (LiteralNode)rhs, operator, order); } else if (rhs instanceof IdNode && lhs instanceof LiteralNode) { - return getOrdering((IdNode)rhs, (LiteralNode)rhs, operator, order); + return getOrdering((IdNode)rhs, (LiteralNode)lhs, operator, order); } return null; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { if (operator.equals("==") || operator.equals("=")) { if (lhs instanceof IdNode && rhs instanceof LiteralNode) { @@ -144,9 +127,9 @@ public class ComparisonNode implements ExpressionNode { } else if (rhs instanceof IdNode && lhs instanceof LiteralNode) { return compare(factory, (IdNode)rhs, (LiteralNode)lhs, operator); } else if (lhs instanceof SearchColumnNode && rhs instanceof LiteralNode) { - return compare(factory, (SearchColumnNode)lhs, (LiteralNode)rhs); + return compare((SearchColumnNode)lhs, (LiteralNode)rhs); } else if (rhs instanceof SearchColumnNode && lhs instanceof LiteralNode) { - return compare(factory, (SearchColumnNode)rhs, (LiteralNode)lhs); + return compare((SearchColumnNode)rhs, (LiteralNode)lhs); } } return null; @@ -155,12 +138,11 @@ public class ComparisonNode implements ExpressionNode { /** * Compares a search column node with a literal node. * - * @param factory The bucket id factory used. * @param node The search column node. * @param literal The literal node to compare to. * @return The bucket set containing the buckets covered. */ - private BucketSet compare(BucketIdFactory factory, SearchColumnNode node, LiteralNode literal) { + private BucketSet compare(SearchColumnNode node, LiteralNode literal) { Object value = literal.getValue(); int bucketCount = (int) Math.pow(2, 16); if (value instanceof Long) { @@ -211,7 +193,7 @@ public class ComparisonNode implements ExpressionNode { return null; } - // Inherit doc from Node. + @Override public Object evaluate(Context context) { Object oLeft = lhs.evaluate(context); Object oRight = rhs.evaluate(context); @@ -254,7 +236,7 @@ public class ComparisonNode implements ExpressionNode { } } - public ResultList evaluateListsTrue(AttributeNode.VariableValueList lhs, AttributeNode.VariableValueList rhs) { + private ResultList evaluateListsTrue(AttributeNode.VariableValueList lhs, AttributeNode.VariableValueList rhs) { if (lhs.size() != rhs.size()) { return new ResultList(Result.FALSE); } @@ -272,7 +254,7 @@ public class ComparisonNode implements ExpressionNode { return new ResultList(Result.TRUE); } - public ResultList evaluateListsFalse(AttributeNode.VariableValueList lhs, AttributeNode.VariableValueList rhs) { + private ResultList evaluateListsFalse(AttributeNode.VariableValueList lhs, AttributeNode.VariableValueList rhs) { ResultList lst = evaluateListsTrue(lhs, rhs); if (lst.toResult() == Result.TRUE) { return new ResultList(Result.FALSE); @@ -283,7 +265,7 @@ public class ComparisonNode implements ExpressionNode { } } - public ResultList evaluateListAndSingle(AttributeNode.VariableValueList lhs, Object rhs) { + private ResultList evaluateListAndSingle(AttributeNode.VariableValueList lhs, Object rhs) { if (rhs == null && lhs == null) { return new ResultList(Result.TRUE); } @@ -293,9 +275,9 @@ public class ComparisonNode implements ExpressionNode { } ResultList retVal = new ResultList(); - for (int i = 0; i < lhs.size(); i++) { - Result result = evaluateBool(lhs.get(i).getValue(), rhs); - retVal.add((FieldPathIteratorHandler.VariableMap)lhs.get(i).getVariables().clone(), result); + for (ResultList.VariableValue value : lhs) { + Result result = evaluateBool(value.getValue(), rhs); + retVal.add((FieldPathIteratorHandler.VariableMap)value.getVariables().clone(), result); } return retVal; @@ -440,11 +422,11 @@ public class ComparisonNode implements ExpressionNode { } } + @Override public void accept(Visitor visitor) { visitor.visit(this); } - // Inherit doc from Object. @Override public String toString() { return lhs + " " + operator + " " + rhs; diff --git a/document/src/main/java/com/yahoo/document/select/rule/DocumentNode.java b/document/src/main/java/com/yahoo/document/select/rule/DocumentNode.java index c0907693dab..9b766a56eb3 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/DocumentNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/DocumentNode.java @@ -1,7 +1,13 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.document.select.rule; -import com.yahoo.document.*; +import com.yahoo.document.BucketIdFactory; +import com.yahoo.document.DocumentGet; +import com.yahoo.document.DocumentOperation; +import com.yahoo.document.DocumentPut; +import com.yahoo.document.DocumentRemove; +import com.yahoo.document.DocumentType; +import com.yahoo.document.DocumentUpdate; import com.yahoo.document.select.BucketSet; import com.yahoo.document.select.Context; import com.yahoo.document.select.OrderingSpecification; diff --git a/document/src/main/java/com/yahoo/document/select/rule/EmbracedNode.java b/document/src/main/java/com/yahoo/document/select/rule/EmbracedNode.java index e1bd4b2d53f..7bf3413bafb 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/EmbracedNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/EmbracedNode.java @@ -27,11 +27,12 @@ public class EmbracedNode implements ExpressionNode { return this; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return node.getBucketSet(factory); } + @Override public Object evaluate(Context context) { return node.evaluate(context); } @@ -41,10 +42,12 @@ public class EmbracedNode implements ExpressionNode { return "(" + node + ")"; } + @Override public void accept(Visitor visitor) { visitor.visit(this); } + @Override public OrderingSpecification getOrdering(int order) { return null; } diff --git a/document/src/main/java/com/yahoo/document/select/rule/ExpressionNode.java b/document/src/main/java/com/yahoo/document/select/rule/ExpressionNode.java index 29f49c4459c..9350b8ac1c5 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/ExpressionNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/ExpressionNode.java @@ -21,14 +21,14 @@ public interface ExpressionNode { * @param doc The document to evaluate over. * @return The value of this. */ - public Object evaluate(Context doc); + Object evaluate(Context doc); /** * Returns the set of bucket ids covered by this node. * * @param factory The factory used by the current application. */ - public BucketSet getBucketSet(BucketIdFactory factory); + BucketSet getBucketSet(BucketIdFactory factory); /** * If this document selection implies a specific ordering (using the orderdoc scheme), @@ -36,13 +36,13 @@ public interface ExpressionNode { * * @param order The order in which we are looking to traverse the ordering (ASCENDING or DESCENDING) */ - public OrderingSpecification getOrdering(int order); + OrderingSpecification getOrdering(int order); /** * Perform visitation of this node. * * @param visitor The visitor that wishes to visit the node. */ - public void accept(Visitor visitor); + void accept(Visitor visitor); } diff --git a/document/src/main/java/com/yahoo/document/select/rule/IdNode.java b/document/src/main/java/com/yahoo/document/select/rule/IdNode.java index 3c15a2866cc..3ca53c1d07d 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/IdNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/IdNode.java @@ -3,8 +3,11 @@ package com.yahoo.document.select.rule; import com.yahoo.document.DocumentId; import com.yahoo.document.BucketIdFactory; -import com.yahoo.document.select.*; -import com.yahoo.document.idstring.*; +import com.yahoo.document.idstring.OrderDocIdString; +import com.yahoo.document.select.BucketSet; +import com.yahoo.document.select.Context; +import com.yahoo.document.select.OrderingSpecification; +import com.yahoo.document.select.Visitor; /** * @author Simon Thoresen Hult @@ -46,7 +49,7 @@ public class IdNode implements ExpressionNode { return divisionBits; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return null; } @@ -55,7 +58,7 @@ public class IdNode implements ExpressionNode { return null; } - // Inherit doc from ExpressionNode. + @Override public Object evaluate(Context context) { DocumentId id = context.getDocumentOperation().getId(); if (id == null) { @@ -97,6 +100,7 @@ public class IdNode implements ExpressionNode { return null; } + @Override public void accept(Visitor visitor) { visitor.visit(this); } diff --git a/document/src/main/java/com/yahoo/document/select/rule/LogicNode.java b/document/src/main/java/com/yahoo/document/select/rule/LogicNode.java index a7b112fac70..1ae397398b3 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/LogicNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/LogicNode.java @@ -30,7 +30,7 @@ public class LogicNode implements ExpressionNode { public static final int AND = 2; // The items contained in this. - private final List<NodeItem> items = new ArrayList<NodeItem>(); + private final List<NodeItem> items = new ArrayList<>(); /** * Construct an empty logic expression. @@ -55,7 +55,7 @@ public class LogicNode implements ExpressionNode { return this; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { Stack<BucketItem> buf = new Stack<>(); for (NodeItem item : items) { @@ -72,6 +72,7 @@ public class LogicNode implements ExpressionNode { return buf.pop().buckets; } + @Override public OrderingSpecification getOrdering(int order) { Stack<OrderingItem> buf = new Stack<>(); for (NodeItem item : items) { @@ -159,23 +160,21 @@ public class LogicNode implements ExpressionNode { buf.push(lhs); } - // Inherit doc from ExpressionNode. @Override public Object evaluate(Context context) { Stack<ValueItem> buf = new Stack<>(); for (NodeItem item : items) { - if ( ! buf.isEmpty()) { - while (buf.peek().operator > item.operator) { + if ( buf.size() > 1) { + while ((buf.peek().getOperator() >= item.operator)) { combineValues(buf); } } - - buf.push(new ValueItem(item.operator, ResultList.toResultList(item.node.evaluate(context)))); + buf.push(new LazyValueItem(item, context)); } while (buf.size() > 1) { combineValues(buf); } - return buf.pop().value; + return buf.pop().getResult(); } /** @@ -186,24 +185,14 @@ public class LogicNode implements ExpressionNode { private void combineValues(Stack<ValueItem> buf) { ValueItem rhs = buf.pop(); ValueItem lhs = buf.pop(); - - switch (rhs.operator) { - case AND: - buf.push(new ValueItem(lhs.operator, lhs.value.combineAND(rhs.value))); - break; - case OR: - buf.push(new ValueItem(lhs.operator, lhs.value.combineOR(rhs.value))); - break; - default: - throw new IllegalStateException("Arithmetic operator " + rhs.operator + " not supported."); - } + buf.push(new LazyCombinedItem(lhs, rhs)); } + @Override public void accept(Visitor visitor) { visitor.visit(this); } - // Inherit doc from Object. @Override public String toString() { StringBuilder ret = new StringBuilder(); @@ -222,7 +211,7 @@ public class LogicNode implements ExpressionNode { * @param operator The operator index to convert. * @return The string representation. */ - public String operatorToString(int operator) { + private String operatorToString(int operator) { switch (operator) { case NOP: return null; @@ -257,13 +246,58 @@ public class LogicNode implements ExpressionNode { /** * Private class to store results in a stack. */ - private final class ValueItem { - private int operator; - private ResultList value; - - public ValueItem(int operator, ResultList value) { + private abstract class ValueItem implements ResultList.LazyResultList { + private final int operator; + ValueItem(int operator) { this.operator = operator; - this.value = value; + } + int getOperator() { return operator; } + } + + private final class LazyValueItem extends ValueItem { + private final NodeItem item; + private final Context context; + private ResultList lazyResult = null; + + LazyValueItem(NodeItem item, Context context) { + super(item.operator); + this.item = item; + this.context = context; + } + @Override + public ResultList getResult() { + if (lazyResult == null) { + lazyResult = ResultList.toResultList(item.node.evaluate(context)); + } + return lazyResult; + } + } + + private final class LazyCombinedItem extends ValueItem { + private final ValueItem lhs; + private final ValueItem rhs; + private ResultList lazyResult = null; + + LazyCombinedItem(ValueItem lhs, ValueItem rhs) { + super(lhs.getOperator()); + this.lhs = lhs; + this.rhs = rhs; + } + @Override + public ResultList getResult() { + if (lazyResult == null) { + switch (rhs.getOperator()) { + case AND: + lazyResult = lhs.getResult().combineAND(rhs); + break; + case OR: + lazyResult = lhs.getResult().combineOR(rhs); + break; + default: + throw new IllegalStateException("Logical operator " + rhs.getOperator() + " not supported."); + } + } + return lazyResult; } } @@ -274,7 +308,7 @@ public class LogicNode implements ExpressionNode { private int operator; private BucketSet buckets; - public BucketItem(int operator, BucketSet buckets) { + BucketItem(int operator, BucketSet buckets) { this.operator = operator; this.buckets = buckets; } @@ -287,7 +321,7 @@ public class LogicNode implements ExpressionNode { private int operator; private OrderingSpecification ordering; - public OrderingItem(int operator, OrderingSpecification orderSpec) { + OrderingItem(int operator, OrderingSpecification orderSpec) { this.operator = operator; this.ordering = orderSpec; } @@ -300,7 +334,7 @@ public class LogicNode implements ExpressionNode { private int operator; private ExpressionNode node; - public NodeItem(int operator, ExpressionNode node) { + NodeItem(int operator, ExpressionNode node) { this.operator = operator; this.node = node; } diff --git a/document/src/main/java/com/yahoo/document/select/rule/NegationNode.java b/document/src/main/java/com/yahoo/document/select/rule/NegationNode.java index c89759bbf07..ba75b13747d 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/NegationNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/NegationNode.java @@ -28,16 +28,17 @@ public class NegationNode implements ExpressionNode { return this; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return null; } - // Inherit doc from ExpressionNode. + @Override public Object evaluate(Context context) { return Result.invert(Result.toResult(node.evaluate(context))); } + @Override public void accept(Visitor visitor) { visitor.visit(this); } @@ -47,6 +48,7 @@ public class NegationNode implements ExpressionNode { return "not " + node; } + @Override public OrderingSpecification getOrdering(int order) { return null; } diff --git a/document/src/main/java/com/yahoo/document/select/rule/NowNode.java b/document/src/main/java/com/yahoo/document/select/rule/NowNode.java index 900423addff..567d9ddcb1f 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/NowNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/NowNode.java @@ -1,22 +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.document.select.rule; + import com.yahoo.document.BucketIdFactory; -import com.yahoo.document.select.*; +import com.yahoo.document.select.BucketSet; +import com.yahoo.document.select.Context; +import com.yahoo.document.select.OrderingSpecification; +import com.yahoo.document.select.Visitor; /** * @author Ulf Lilleengen */ public class NowNode implements ExpressionNode { - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return null; } - // Inherit doc from ExpressionNode. + @Override public Object evaluate(Context context) { - Object ret = System.currentTimeMillis() / 1000; - return ret; + return System.currentTimeMillis() / 1000; } @Override @@ -24,10 +27,11 @@ public class NowNode implements ExpressionNode { return "now()"; } + @Override public OrderingSpecification getOrdering(int order) { return null; } - + @Override public void accept(Visitor visitor) { visitor.visit(this); } diff --git a/document/src/main/java/com/yahoo/document/select/rule/SearchColumnNode.java b/document/src/main/java/com/yahoo/document/select/rule/SearchColumnNode.java index ffcf576dcfc..071e51c192f 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/SearchColumnNode.java +++ b/document/src/main/java/com/yahoo/document/select/rule/SearchColumnNode.java @@ -3,7 +3,10 @@ package com.yahoo.document.select.rule; import com.yahoo.document.BucketDistribution; import com.yahoo.document.BucketIdFactory; -import com.yahoo.document.select.*; +import com.yahoo.document.select.BucketSet; +import com.yahoo.document.select.Context; +import com.yahoo.document.select.OrderingSpecification; +import com.yahoo.document.select.Visitor; /** * @author Simon Thoresen Hult @@ -31,16 +34,17 @@ public class SearchColumnNode implements ExpressionNode { return distribution; } - // Inherit doc from ExpressionNode. + @Override public BucketSet getBucketSet(BucketIdFactory factory) { return null; } - // Inherit doc from ExpressionNode. + @Override public Object evaluate(Context context) { return distribution.getColumn(factory.getBucketId(context.getDocumentOperation().getId())); } + @Override public void accept(Visitor visitor) { visitor.visit(this); } @@ -50,6 +54,7 @@ public class SearchColumnNode implements ExpressionNode { return "searchcolumn." + field; } + @Override public OrderingSpecification getOrdering(int order) { return null; } diff --git a/document/src/main/java/com/yahoo/document/select/rule/package-info.java b/document/src/main/java/com/yahoo/document/select/rule/package-info.java index 0b80dd5f25d..6c643397264 100644 --- a/document/src/main/java/com/yahoo/document/select/rule/package-info.java +++ b/document/src/main/java/com/yahoo/document/select/rule/package-info.java @@ -1,7 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. @ExportPackage -@PublicApi package com.yahoo.document.select.rule; -import com.yahoo.api.annotations.PublicApi; import com.yahoo.osgi.annotation.ExportPackage; diff --git a/document/src/main/java/com/yahoo/document/select/simple/IdSpecParser.java b/document/src/main/java/com/yahoo/document/select/simple/IdSpecParser.java index f792c4b8c0f..db4d46e871b 100644 --- a/document/src/main/java/com/yahoo/document/select/simple/IdSpecParser.java +++ b/document/src/main/java/com/yahoo/document/select/simple/IdSpecParser.java @@ -9,7 +9,7 @@ import com.yahoo.document.select.rule.IdNode; public class IdSpecParser extends Parser { private IdNode id; public IdNode getId() { return id; } - public boolean isUserSpec() { return "user".equals(id.getField()); } + boolean isUserSpec() { return "user".equals(id.getField()); } public boolean parse(CharSequence s) { boolean retval = false; int pos = eatWhite(s); diff --git a/document/src/main/java/com/yahoo/document/select/simple/StringParser.java b/document/src/main/java/com/yahoo/document/select/simple/StringParser.java index d43c57aee03..50d4915e360 100644 --- a/document/src/main/java/com/yahoo/document/select/simple/StringParser.java +++ b/document/src/main/java/com/yahoo/document/select/simple/StringParser.java @@ -14,7 +14,7 @@ public class StringParser extends Parser { int pos = eatWhite(s); if (pos + 1 < s.length()) { if (s.charAt(pos++) == '"') { - StringBuffer str = new StringBuffer(""); + StringBuilder str = new StringBuilder(); for(; (pos < s.length()) && (s.charAt(pos) != '"');pos++) { if ((pos < s.length()) && (s.charAt(pos) == '\\')) { pos++; diff --git a/document/src/main/java/com/yahoo/document/select/simple/package-info.java b/document/src/main/java/com/yahoo/document/select/simple/package-info.java index e6885d54fd4..936d63a7697 100644 --- a/document/src/main/java/com/yahoo/document/select/simple/package-info.java +++ b/document/src/main/java/com/yahoo/document/select/simple/package-info.java @@ -1,7 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. @ExportPackage -@PublicApi package com.yahoo.document.select.simple; -import com.yahoo.api.annotations.PublicApi; import com.yahoo.osgi.annotation.ExportPackage; diff --git a/document/src/test/java/com/yahoo/document/select/LogicalNodeTestCase.java b/document/src/test/java/com/yahoo/document/select/LogicalNodeTestCase.java new file mode 100644 index 00000000000..25aca22b108 --- /dev/null +++ b/document/src/test/java/com/yahoo/document/select/LogicalNodeTestCase.java @@ -0,0 +1,126 @@ +package com.yahoo.document.select; + +import com.yahoo.document.BucketIdFactory; +import com.yahoo.document.select.rule.ExpressionNode; +import com.yahoo.document.select.rule.LiteralNode; +import com.yahoo.document.select.rule.LogicNode; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LogicalNodeTestCase { + private static class TracedNode implements ExpressionNode { + private final AtomicInteger evalOrder; + private final ExpressionNode node; + private int evaluatedAs = -1; + + TracedNode(AtomicInteger evalOrder, ExpressionNode node) { + this.evalOrder = evalOrder; + this.node = node; + } + @Override + public Object evaluate(Context doc) { + evaluatedAs = evalOrder.getAndIncrement(); + return node.evaluate(doc); + } + + @Override + public BucketSet getBucketSet(BucketIdFactory factory) { + return node.getBucketSet(factory); + } + + @Override + public OrderingSpecification getOrdering(int order) { + return node.getOrdering(order); + } + + @Override + public void accept(Visitor visitor) { + node.accept(visitor); + } + boolean isEvaluated() { return evaluatedAs >= 0; } + int getEvalOrder() { return evaluatedAs; } + } + private static Result evaluate(ExpressionNode node) { + return ((ResultList)node.evaluate(new Context(null))).toResult(); + } + + private static TracedNode createTraced(AtomicInteger evalOrder, char node) { + return new TracedNode(evalOrder, new LiteralNode(node == 'T')); + } + + private static void addOperator(LogicNode logical, char operator, ExpressionNode node) { + if (operator == '&') { + logical.add("and", node); + } else if (operator == '|') { + logical.add("or", node); + } else { + throw new IllegalArgumentException("Bad operator '" + operator + "'"); + } + } + + static private void verifyEvaluationOrder(String expr, boolean expectedResult, List<Integer> expectedEvaluationOrder ) { + assertEquals(1, expr.length()%2); + assertEquals(expectedEvaluationOrder.size()*2 - 1, expr.length()); + TracedNode [] traced = new TracedNode[expectedEvaluationOrder.size()]; + AtomicInteger evalOrder = new AtomicInteger(0); + for (int i=0; i < traced.length; i++) { + traced[i] = createTraced(evalOrder, expr.charAt(i*2)); + } + LogicNode logical = new LogicNode().add(null, traced[0]); + for (int i=1; i < traced.length; i++) { + addOperator(logical, expr.charAt(i*2-1), traced[i]); + } + for (TracedNode node : traced) { + assertFalse(node.isEvaluated()); + } + assertEquals(Result.toResult(expectedResult), evaluate(logical)); + for (int i = 0; i < traced.length; i++) { + assertEquals(expectedEvaluationOrder.get(i).intValue(), traced[i].getEvalOrder()); + } + } + @Test + public void testFullyExhaustedAND() { + verifyEvaluationOrder("T&T", true, List.of(0,1)); + + } + @Test + public void testShortCircuitAND() { + verifyEvaluationOrder("F&T", false, List.of(0,-1)); + } + + @Test + public void testFullyExhaustedOR() { + verifyEvaluationOrder("F|T", true, List.of(0,1)); + } + + @Test + public void testShortCircuitOR() { + verifyEvaluationOrder("T|F", true, List.of(0,-1)); + } + + @Test + public void testLeft2Right() { + verifyEvaluationOrder("T&T&T&T&T", true, List.of(0,1,2,3,4)); + verifyEvaluationOrder("T&T&F&T&F", false, List.of(0,1,2,-1,-1)); + + verifyEvaluationOrder("F|F|F|F|T", true, List.of(0,1,2,3,4)); + verifyEvaluationOrder("F|F|F|F|F", false, List.of(0,1,2,3,4)); + verifyEvaluationOrder("F|F|T|F|T", true, List.of(0,1,2,-1,-1)); + } + + @Test + public void testLeft2RightWithPriority() { + verifyEvaluationOrder("T&F|T", true, List.of(0,1,2)); + verifyEvaluationOrder("F&T|T", true, List.of(0,-1,1)); + + verifyEvaluationOrder("T|F&T", true, List.of(0,-1,-1)); + verifyEvaluationOrder("F|F&T", false, List.of(0,1,-1)); + verifyEvaluationOrder("F|T&T", true, List.of(0,1,2)); + } +} diff --git a/document/src/tests/documenttestcase.cpp b/document/src/tests/documenttestcase.cpp index bb07f63a83b..5521fac58e8 100644 --- a/document/src/tests/documenttestcase.cpp +++ b/document/src/tests/documenttestcase.cpp @@ -1161,7 +1161,7 @@ TEST(DocumentTest, testCompressionConfigured) Struct("serializetest.body").setId(45) .addField("stringfield", DataType::T_STRING) .setCompression(DocumenttypesConfig::Documenttype:: - Datatype::Sstruct::Compression::LZ4, + Datatype::Sstruct::Compression::Type::LZ4, 9, 99, 0)); DocumentTypeRepo repo2(builder2.config()); diff --git a/document/src/tests/gid_filter_test.cpp b/document/src/tests/gid_filter_test.cpp index dcfafb56b99..fec6581b8f7 100644 --- a/document/src/tests/gid_filter_test.cpp +++ b/document/src/tests/gid_filter_test.cpp @@ -19,8 +19,10 @@ protected: Fixture(vespalib::stringref selection); ~Fixture(); - Fixture(Fixture&&) = default; - Fixture& operator=(Fixture&&) = default; + Fixture(const Fixture&) = delete; + Fixture(Fixture&&) = delete; + Fixture& operator=(const Fixture&) = delete; + Fixture& operator=(Fixture&&) = delete; static Fixture for_selection(vespalib::stringref s) { return Fixture(s); diff --git a/document/src/tests/repo/documenttyperepo_test.cpp b/document/src/tests/repo/documenttyperepo_test.cpp index 64900d2ad65..b263ad75930 100644 --- a/document/src/tests/repo/documenttyperepo_test.cpp +++ b/document/src/tests/repo/documenttyperepo_test.cpp @@ -86,7 +86,7 @@ TEST("requireThatStructsCanConfigureCompression") { builder.document(doc_type_id, type_name, Struct(header_name), Struct(body_name).setCompression( - Sstruct::Compression::LZ4, + Sstruct::Compression::Type::LZ4, comp_level, comp_minres, comp_minsize)); DocumentTypeRepo repo(builder.config()); diff --git a/document/src/vespa/document/repo/configbuilder.cpp b/document/src/vespa/document/repo/configbuilder.cpp index 9610697b84f..7d9b607facd 100644 --- a/document/src/vespa/document/repo/configbuilder.cpp +++ b/document/src/vespa/document/repo/configbuilder.cpp @@ -62,8 +62,8 @@ DocTypeRep DocumenttypesConfigBuilderHelper::document(int32_t id, const vespalib::string &name, const DatatypeConfig &header, const DatatypeConfig &body) { - assert(header.type == DatatypeConfig::STRUCT); - assert(body.type == DatatypeConfig::STRUCT); + assert(header.type == DatatypeConfig::Type::STRUCT); + assert(body.type == DatatypeConfig::Type::STRUCT); _config.documenttype.resize(_config.documenttype.size() + 1); _config.documenttype.back().id = id; _config.documenttype.back().name = name; diff --git a/document/src/vespa/document/repo/configbuilder.h b/document/src/vespa/document/repo/configbuilder.h index 5f6f5548ae2..15ee0da0c79 100644 --- a/document/src/vespa/document/repo/configbuilder.h +++ b/document/src/vespa/document/repo/configbuilder.h @@ -39,7 +39,7 @@ struct TypeOrId { struct Struct : DatatypeConfig { Struct(const vespalib::string &name) { - type = STRUCT; + type = Type::STRUCT; sstruct.name = name; } Struct &setCompression(Sstruct::Compression::Type t, int32_t level, @@ -65,7 +65,7 @@ struct Struct : DatatypeConfig { struct Array : DatatypeConfig { Array(TypeOrId nested_type) { addNestedType(nested_type); - type = ARRAY; + type = Type::ARRAY; array.element.id = nested_type.id; } }; @@ -73,7 +73,7 @@ struct Array : DatatypeConfig { struct Wset : DatatypeConfig { Wset(TypeOrId nested_type) { addNestedType(nested_type); - type = WSET; + type = Type::WSET; wset.key.id = nested_type.id; } Wset &removeIfZero() { wset.removeifzero = true; return *this; } @@ -87,7 +87,7 @@ struct Map : DatatypeConfig { Map(TypeOrId key_type, TypeOrId value_type) { addNestedType(key_type); addNestedType(value_type); - type = MAP; + type = Type::MAP; map.key.id = key_type.id; map.value.id = value_type.id; } @@ -95,7 +95,7 @@ struct Map : DatatypeConfig { struct AnnotationRef : DatatypeConfig { AnnotationRef(int32_t annotation_type_id) { - type = ANNOTATIONREF; + type = Type::ANNOTATIONREF; annotationref.annotation.id = annotation_type_id; } }; diff --git a/document/src/vespa/document/repo/documenttyperepo.cpp b/document/src/vespa/document/repo/documenttyperepo.cpp index bdecd521f44..da59f527115 100644 --- a/document/src/vespa/document/repo/documenttyperepo.cpp +++ b/document/src/vespa/document/repo/documenttyperepo.cpp @@ -310,7 +310,7 @@ void addStruct(int32_t id, const Datatype::Sstruct &s, Repo &repo) { } CompressionConfig::Type type = CompressionConfig::NONE; - if (s.compression.type == Datatype::Sstruct::Compression::LZ4) { + if (s.compression.type == Datatype::Sstruct::Compression::Type::LZ4) { type = CompressionConfig::LZ4; } @@ -348,18 +348,18 @@ void addAnnotationRef(int32_t id, const Datatype::Annotationref &a, Repo &r, con void addDataType(const Datatype &type, Repo &repo, const AnnotationTypeRepo &a_repo) { switch (type.type) { - case Datatype::STRUCT: + case Datatype::Type::STRUCT: return addStruct(type.id, type.sstruct, repo); - case Datatype::ARRAY: + case Datatype::Type::ARRAY: return addArray(type.id, type.array, repo); - case Datatype::WSET: + case Datatype::Type::WSET: return addWset(type.id, type.wset, repo); - case Datatype::MAP: + case Datatype::Type::MAP: return addMap(type.id, type.map, repo); - case Datatype::ANNOTATIONREF: + case Datatype::Type::ANNOTATIONREF: return addAnnotationRef(type.id, type.annotationref, repo, a_repo); default: - throw IllegalArgumentException(make_string("Unknown datatype type %d for id %d", type.type, type.id)); + throw IllegalArgumentException(make_string("Unknown datatype type %d for id %d", static_cast<int>(type.type), type.id)); } } diff --git a/documentapi/src/main/java/com/yahoo/documentapi/SyncParameters.java b/documentapi/src/main/java/com/yahoo/documentapi/SyncParameters.java index acf027dd0aa..29d04c85a05 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/SyncParameters.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/SyncParameters.java @@ -11,6 +11,7 @@ import java.util.Optional; * @author Simon Thoresen Hult */ public class SyncParameters extends Parameters { + private final Duration defaultTimeout; private SyncParameters() { @@ -26,6 +27,7 @@ public class SyncParameters extends Parameters { } public static class Builder { + private Duration defaultTimeout; /** @@ -39,4 +41,5 @@ public class SyncParameters extends Parameters { return new SyncParameters(defaultTimeout); } } + } diff --git a/documentapi/src/main/java/com/yahoo/documentapi/SyncSession.java b/documentapi/src/main/java/com/yahoo/documentapi/SyncSession.java index 418c0374193..6c4306b683c 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/SyncSession.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/SyncSession.java @@ -51,13 +51,11 @@ public interface SyncSession extends Session { /** * Gets a document with an unspecified timeout * - * @param id The id of the document to get. - * @param fieldSet A comma-separated list of fields to retrieve - * @param priority The priority with which to perform this operation. - * @return The known document having this id, or null if there is no - * document having this id. - * @throws UnsupportedOperationException Thrown if this access does not - * support retrieving. + * @param id the id of the document to get + * @param fieldSet a comma-separated list of fields to retrieve + * @param priority the priority with which to perform this operation + * @return the document with this id, or null if there is none + * @throws UnsupportedOperationException thrown if this does not support retrieving */ default Document get(DocumentId id, String fieldSet, DocumentProtocol.Priority priority) { return get(id, fieldSet, priority, null); @@ -66,11 +64,10 @@ public interface SyncSession extends Session { /** * Gets a document with timeout. * - * @param id The id of the document to get. - * @param timeout Timeout. If timeout is null, an unspecified default will be used. - * @return The known document having this id, or null if there is no - * document having this id. - * @throws UnsupportedOperationException Thrown if this access does not support retrieving. + * @param id The id of the document to get + * @param timeout Timeout. If timeout is null, an unspecified default will be used + * @return the document with this id, or null if there is none + * @throws UnsupportedOperationException thrown if this access does not support retrieving * @throws DocumentAccessException on any messagebus error, including timeout ({@link com.yahoo.messagebus.ErrorCode#TIMEOUT}). */ Document get(DocumentId id, Duration timeout); diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java index a2bad9c84e1..33286d227bf 100644 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java @@ -143,6 +143,14 @@ public class MessageBusAsyncSession implements MessageBusSession, AsyncSession { return send(msg); } + private boolean mayOverrideWithGetOnlyRoute(Message msg) { + // Only allow implicitly overriding the default Get route if the message is attempted sent + // with the default route originally. Otherwise it's reasonable to assume that the caller + // has some explicit idea of why the regular route is set to the value it is. + return ((msg.getType() == DocumentProtocol.MESSAGE_GETDOCUMENT) + && ("default".equals(route) || "route:default".equals(route))); + } + /** * A convenience method for assigning the internal trace level and route string to a message before sending it * through the internal mbus session object. @@ -155,7 +163,7 @@ public class MessageBusAsyncSession implements MessageBusSession, AsyncSession { long reqId = requestId.incrementAndGet(); msg.setContext(reqId); msg.getTrace().setLevel(traceLevel); - String toRoute = (msg.getType() == DocumentProtocol.MESSAGE_GETDOCUMENT ? routeForGet : route); + String toRoute = (mayOverrideWithGetOnlyRoute(msg) ? routeForGet : route); if (toRoute != null) { return toResult(reqId, session.send(msg, toRoute, true)); } else { diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/PolicyTestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/PolicyTestCase.java index fd5f43c23c1..cb32ce75e03 100755 --- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/PolicyTestCase.java +++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/PolicyTestCase.java @@ -47,6 +47,7 @@ import com.yahoo.messagebus.routing.RoutingSpec; import com.yahoo.messagebus.routing.RoutingTableSpec; import com.yahoo.messagebus.test.Receptor; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; @@ -60,7 +61,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -68,7 +69,6 @@ import static org.junit.Assert.assertTrue; /** * @author Simon Thoresen Hult */ -@SuppressWarnings("deprecation") public class PolicyTestCase { private static final int TIMEOUT = 300; @@ -313,7 +313,7 @@ public class PolicyTestCase { for (int i = 0; i < 10; ++i) { RoutingNode leaf = frame.select(1).get(0); String recipient = leaf.getRoute().toString(); - assertTrue(recipient.equals("docproc/cluster.default/*/chain.default")); + assertEquals(recipient, "docproc/cluster.default/*/chain.default"); lst.add(recipient); leaf.handleReply(new EmptyReply()); @@ -496,7 +496,7 @@ public class PolicyTestCase { if (prev == null) { assertNotNull(next); } else { - assertFalse(prev.equals(next)); + assertNotEquals(prev, next); } prev = next; leaf.handleReply(new EmptyReply()); @@ -612,6 +612,32 @@ public class PolicyTestCase { frame.destroy(); } + @Test + public void testDocumentSelectorDualCluster() { + PolicyTestFrame frame = new PolicyTestFrame(manager); + frame.setHop(new HopSpec("test", "[DocumentRouteSelector:raw:" + + "route[2]\n" + + "route[0].name \"foo\"\n" + + "route[0].selector \"(testdoc AND (testdoc.intfield / 1000 > 0))\"\n" + + "route[0].feed \"myfeed\"\n" + + "route[1].name \"bar\"\n" + + "route[1].selector \"(other AND (other.intfield / 1000 > 0))\"\n" + + "route[1].feed \"myfeed\"\n]").addRecipient("foo").addRecipient("bar")); + + frame.setMessage(new GetDocumentMessage(new DocumentId("doc:scheme:"), "fieldSet")); + frame.assertSelect(Arrays.asList("bar", "foo")); + + Document doc = new Document(manager.getDocumentType("testdoc"), new DocumentId("doc:scheme:")); + doc.setFieldValue("intfield", 3000); + Message put = new PutDocumentMessage(new DocumentPut(doc)); + frame.setMessage(put); + frame.assertSelect(Arrays.asList("foo")); + + frame.setMessage(put); + frame.assertMergeOneReply("foo"); + + frame.destroy(); + } @Test public void testDocumentRouteSelectorIgnore() { @@ -676,7 +702,7 @@ public class PolicyTestCase { assertSelect(frame, 32, Arrays.asList("docproc/cluster.default/9/chain.default")); frame.getNetwork().unregisterSession("9/chain.default"); assertTrue(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 7)); - assertSelect(frame, 32, new ArrayList<String>()); + assertSelect(frame, 32, new ArrayList<>()); // Test merge behavior. frame.setHop(new HopSpec("test", "[RoundRobin]").addRecipient("docproc/cluster.default/0/chain.default")); diff --git a/eval/src/tests/eval/gbdt/gbdt_benchmark.cpp b/eval/src/tests/eval/gbdt/gbdt_benchmark.cpp index 230f1ed251e..20e04c9593e 100644 --- a/eval/src/tests/eval/gbdt/gbdt_benchmark.cpp +++ b/eval/src/tests/eval/gbdt/gbdt_benchmark.cpp @@ -95,7 +95,7 @@ std::vector<Option> all_options({{0, none},{1, vm_forest}}); struct Result { double us; size_t opt_idx; - bool operator<(const Result &rhs) { + bool operator<(const Result &rhs) const { return (us < rhs.us); } }; diff --git a/eval/src/tests/tensor/dense_add_dimension_optimizer/dense_add_dimension_optimizer_test.cpp b/eval/src/tests/tensor/dense_add_dimension_optimizer/dense_add_dimension_optimizer_test.cpp index eaf4623afea..274117ea693 100644 --- a/eval/src/tests/tensor/dense_add_dimension_optimizer/dense_add_dimension_optimizer_test.cpp +++ b/eval/src/tests/tensor/dense_add_dimension_optimizer/dense_add_dimension_optimizer_test.cpp @@ -25,6 +25,7 @@ const TensorEngine &prod_engine = DefaultTensorEngine::ref(); EvalFixture::ParamRepo make_params() { return EvalFixture::ParamRepo() .add("x5", spec({x(5)}, N())) + .add("x5f", spec(float_cells({x(5)}), N())) .add("x5y1", spec({x(5),y(1)}, N())) .add("y1z1", spec({y(1),z(1)}, N())) .add("x_m", spec({x({"a"})}, N())); @@ -78,9 +79,9 @@ TEST("require that non-canonical dimension addition is not optimized") { TEST_DO(verify_not_optimized("tensor(y[1])(1)/x5")); } -TEST("require that dimension addition with overlapping dimensions is not optimized") { - TEST_DO(verify_not_optimized("x5y1*tensor(y[1],z[1])(1)")); - TEST_DO(verify_not_optimized("tensor(y[1],z[1])(1)*x5y1")); +TEST("require that dimension addition with overlapping dimensions is optimized") { + TEST_DO(verify_optimized("x5y1*tensor(y[1],z[1])(1)")); + TEST_DO(verify_optimized("tensor(y[1],z[1])(1)*x5y1")); } TEST("require that dimension addition with inappropriate dimensions is not optimized") { @@ -99,8 +100,13 @@ TEST("require that dimension addition optimization requires unit constant tensor TEST_DO(verify_not_optimized("tensor(x[2])(1)*tensor(y[2])(1)")); } -TEST("require that optimization is disabled for tensors with non-double cells") { - TEST_DO(verify_not_optimized("x5*tensor<float>(a[1],b[1],c[1])(1)")); +TEST("require that optimization also works for float cells") { + TEST_DO(verify_optimized("x5*tensor<float>(a[1],b[1],c[1])(1)")); + TEST_DO(verify_optimized("x5f*tensor<float>(a[1],b[1],c[1])(1)")); +} + +TEST("require that optimization is disabled if unit vector would promote tensor cell types") { + TEST_DO(verify_not_optimized("x5f*tensor(a[1],b[1],c[1])(1)")); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_fast_rename_optimizer/dense_fast_rename_optimizer_test.cpp b/eval/src/tests/tensor/dense_fast_rename_optimizer/dense_fast_rename_optimizer_test.cpp index 4995ea89735..55a9414f82b 100644 --- a/eval/src/tests/tensor/dense_fast_rename_optimizer/dense_fast_rename_optimizer_test.cpp +++ b/eval/src/tests/tensor/dense_fast_rename_optimizer/dense_fast_rename_optimizer_test.cpp @@ -72,8 +72,8 @@ TEST("require that chained optimized renames are compacted into a single operati TEST_DO(verify_optimized("rename(rename(x5,x,y),y,z)")); } -TEST("require that optimization is disabled for tensors with non-double cells") { - TEST_DO(verify_not_optimized("rename(x5f,x,y)")); +TEST("require that optimization works for float cells") { + TEST_DO(verify_optimized("rename(x5f,x,y)")); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_inplace_join_function/dense_inplace_join_function_test.cpp b/eval/src/tests/tensor/dense_inplace_join_function/dense_inplace_join_function_test.cpp index 083ed1c7071..80321ac3d22 100644 --- a/eval/src/tests/tensor/dense_inplace_join_function/dense_inplace_join_function_test.cpp +++ b/eval/src/tests/tensor/dense_inplace_join_function/dense_inplace_join_function_test.cpp @@ -144,10 +144,15 @@ TEST("require that inplace join can be debug dumped") { fprintf(stderr, "%s\n", info[0]->as_string().c_str()); } -TEST("require that optimization is disabled for tensors with non-double cells") { - TEST_DO(verify_not_optimized("mut_x5_A-mut_x5f_D")); - TEST_DO(verify_not_optimized("mut_x5f_D-mut_x5_A")); - TEST_DO(verify_not_optimized("mut_x5f_D-mut_x5f_E")); +TEST("require that optimization works with float cells") { + TEST_DO(verify_p0_optimized("mut_x5f_D-mut_x5f_E", 1)); +} + +TEST("require that overwritten value must have same cell type as result") { + TEST_DO(verify_p0_optimized("mut_x5_A-mut_x5f_D", 1)); + TEST_DO(verify_p1_optimized("mut_x5f_D-mut_x5_A", 1)); + TEST_DO(verify_not_optimized("con_x5_A-mut_x5f_D")); + TEST_DO(verify_not_optimized("mut_x5f_D-con_x5_A")); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_inplace_map_function/dense_inplace_map_function_test.cpp b/eval/src/tests/tensor/dense_inplace_map_function/dense_inplace_map_function_test.cpp index 314d3a6186c..f85742b4e0f 100644 --- a/eval/src/tests/tensor/dense_inplace_map_function/dense_inplace_map_function_test.cpp +++ b/eval/src/tests/tensor/dense_inplace_map_function/dense_inplace_map_function_test.cpp @@ -72,8 +72,8 @@ TEST("require that mapped tensors are not optimized") { TEST_DO(verify_not_optimized("map(_x_m,f(x)(x+10))")); } -TEST("require that optimization is disabled for tensors with non-double cells") { - TEST_DO(verify_not_optimized("map(_x5f,f(x)(x+10))")); +TEST("require that optimization works for float cells") { + TEST_DO(verify_optimized("map(_x5f,f(x)(x+10))", 1)); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_remove_dimension_optimizer/dense_remove_dimension_optimizer_test.cpp b/eval/src/tests/tensor/dense_remove_dimension_optimizer/dense_remove_dimension_optimizer_test.cpp index 7856775ae30..179fdd3eff4 100644 --- a/eval/src/tests/tensor/dense_remove_dimension_optimizer/dense_remove_dimension_optimizer_test.cpp +++ b/eval/src/tests/tensor/dense_remove_dimension_optimizer/dense_remove_dimension_optimizer_test.cpp @@ -78,8 +78,8 @@ TEST("require that inappropriate tensor types cannot be optimized") { TEST_DO(verify_not_optimized("reduce(x1y5z_m,sum,z)")); } -TEST("require that optimization is disabled for tensors with non-double cells") { - TEST_DO(verify_not_optimized("reduce(x1y5z1f,avg,x)")); +TEST("require that optimization works for float cells") { + TEST_DO(verify_optimized("reduce(x1y5z1f,avg,x)")); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp b/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp index 335aa4791a4..426281686d7 100644 --- a/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp +++ b/eval/src/tests/tensor/dense_xw_product_function/dense_xw_product_function_test.cpp @@ -45,6 +45,7 @@ EvalFixture::ParamRepo make_params() { .add("y1z1", spec({y(1),z(1)}, MyMatSeq())) .add("x2y3", spec({x(2),y(3)}, MyMatSeq())) .add("x2y3f", spec(float_cells({x(2),y(3)}), MyMatSeq())) + .add("y3z2f", spec(float_cells({y(3),z(2)}), MyMatSeq())) .add("x2z3", spec({x(2),z(3)}, MyMatSeq())) .add("y3z2", spec({y(3),z(2)}, MyMatSeq())) .add("x8y5", spec({x(8),y(5)}, MyMatSeq())) @@ -118,10 +119,16 @@ TEST("require that xw product can be debug dumped") { fprintf(stderr, "%s\n", info[0]->as_string().c_str()); } -TEST("require that optimization is disabled for tensors with non-double cells") { - TEST_DO(verify_not_optimized("reduce(y3f*x2y3,sum,y)")); - TEST_DO(verify_not_optimized("reduce(y3*x2y3f,sum,y)")); - TEST_DO(verify_not_optimized("reduce(y3f*x2y3f,sum,y)")); +TEST("require that optimization works for float cells") { + TEST_DO(verify_optimized("reduce(y3f*x2y3,sum,y)", 3, 2, true)); + TEST_DO(verify_optimized("reduce(y3*x2y3f,sum,y)", 3, 2, true)); + TEST_DO(verify_optimized("reduce(y3f*x2y3f,sum,y)", 3, 2, true)); +} + +TEST("require that optimization works for float cells with inconvenient dimension nesting") { + TEST_DO(verify_optimized("reduce(y3f*y3z2,sum,y)", 3, 2, false)); + TEST_DO(verify_optimized("reduce(y3*y3z2f,sum,y)", 3, 2, false)); + TEST_DO(verify_optimized("reduce(y3f*y3z2f,sum,y)", 3, 2, false)); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/vespa/eval/eval/value_type.cpp b/eval/src/vespa/eval/eval/value_type.cpp index fc0f3cc5414..d6ba8e83855 100644 --- a/eval/src/vespa/eval/eval/value_type.cpp +++ b/eval/src/vespa/eval/eval/value_type.cpp @@ -12,21 +12,27 @@ using CellType = ValueType::CellType; using Dimension = ValueType::Dimension; using DimensionList = std::vector<Dimension>; -CellType unify(CellType a, CellType b) { - if (a == b) { - return a; - } else { - return CellType::DOUBLE; +template <typename A, typename B> +CellType unify() { + using type = typename UnifyCellTypes<A,B>::type; + return get_cell_type<type>(); +} + +template <typename A> +CellType unify(CellType b) { + switch (b) { + case CellType::DOUBLE: return unify<A,double>(); + case CellType::FLOAT: return unify<A,float>(); } + abort(); } -CellType unify_cell_type(const ValueType &a, const ValueType &b) { - if (a.is_double()) { - return b.cell_type(); - } else if (b.is_double()) { - return a.cell_type(); +CellType unify(CellType a, CellType b) { + switch (a) { + case CellType::DOUBLE: return unify<double>(b); + case CellType::FLOAT: return unify<float>(b); } - return unify(a.cell_type(), b.cell_type()); + abort(); } size_t my_dimension_index(const std::vector<Dimension> &list, const vespalib::string &name) { @@ -265,6 +271,16 @@ ValueType::join(const ValueType &lhs, const ValueType &rhs) return tensor_type(std::move(result.dimensions), unify(lhs._cell_type, rhs._cell_type)); } +CellType +ValueType::unify_cell_types(const ValueType &a, const ValueType &b) { + if (a.is_double()) { + return b.cell_type(); + } else if (b.is_double()) { + return a.cell_type(); + } + return unify(a.cell_type(), b.cell_type()); +} + ValueType ValueType::concat(const ValueType &lhs, const ValueType &rhs, const vespalib::string &dimension) { @@ -278,7 +294,7 @@ ValueType::concat(const ValueType &lhs, const ValueType &rhs, const vespalib::st if (!find_dimension(result.dimensions, dimension)) { result.dimensions.emplace_back(dimension, 2); } - return tensor_type(std::move(result.dimensions), unify_cell_type(lhs, rhs)); + return tensor_type(std::move(result.dimensions), unify_cell_types(lhs, rhs)); } ValueType diff --git a/eval/src/vespa/eval/eval/value_type.h b/eval/src/vespa/eval/eval/value_type.h index 0eb3e1ca28e..64003e2636e 100644 --- a/eval/src/vespa/eval/eval/value_type.h +++ b/eval/src/vespa/eval/eval/value_type.h @@ -78,15 +78,27 @@ public: static ValueType from_spec(const vespalib::string &spec); vespalib::string to_spec() const; static ValueType join(const ValueType &lhs, const ValueType &rhs); + static CellType unify_cell_types(const ValueType &a, const ValueType &b); static ValueType concat(const ValueType &lhs, const ValueType &rhs, const vespalib::string &dimension); static ValueType either(const ValueType &one, const ValueType &other); }; std::ostream &operator<<(std::ostream &os, const ValueType &type); -// utility template -template <typename T> inline bool check_cell_type(ValueType::CellType type); +// utility templates + +template <typename CT> inline bool check_cell_type(ValueType::CellType type); template <> inline bool check_cell_type<double>(ValueType::CellType type) { return (type == ValueType::CellType::DOUBLE); } template <> inline bool check_cell_type<float>(ValueType::CellType type) { return (type == ValueType::CellType::FLOAT); } +template <typename LCT, typename RCT> struct UnifyCellTypes{}; +template <> struct UnifyCellTypes<double, double> { using type = double; }; +template <> struct UnifyCellTypes<double, float> { using type = double; }; +template <> struct UnifyCellTypes<float, double> { using type = double; }; +template <> struct UnifyCellTypes<float, float> { using type = float; }; + +template <typename CT> inline ValueType::CellType get_cell_type(); +template <> inline ValueType::CellType get_cell_type<double>() { return ValueType::CellType::DOUBLE; } +template <> inline ValueType::CellType get_cell_type<float>() { return ValueType::CellType::FLOAT; } + } // namespace diff --git a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp index 58db90f5557..f1eb9ff1523 100644 --- a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp +++ b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp @@ -37,6 +37,7 @@ using eval::TensorFunction; using eval::TensorSpec; using eval::Value; using eval::ValueType; +using CellType = eval::ValueType::CellType; using vespalib::IllegalArgumentException; using vespalib::make_string; @@ -355,8 +356,7 @@ DefaultTensorEngine::reduce(const Value &a, Aggr aggr, const std::vector<vespali size_t vector_size(const ValueType &type, const vespalib::string &dimension) { if (type.is_double()) { return 1; - } else if ((type.cell_type() == ValueType::CellType::DOUBLE) && - (type.dimensions().size() == 1) && + } else if ((type.dimensions().size() == 1) && (type.dimensions()[0].is_indexed()) && (type.dimensions()[0].name == dimension)) { @@ -366,40 +366,50 @@ size_t vector_size(const ValueType &type, const vespalib::string &dimension) { } } +template <typename OCT> struct CallAppendVector { template <typename CT> - static void call(const ConstArrayRef<CT> &arr, double *&pos) { - for (CT cell : arr) { *pos++ = cell; } + static void call(const ConstArrayRef<CT> &arr, OCT *&pos) { + for (CT cell: arr) { *pos++ = cell; } } }; -void append_vector(double *&pos, const Value &value) { +template <typename OCT> +void append_vector(OCT *&pos, const Value &value) { if (auto tensor = value.as_tensor()) { const DenseTensorView *view = static_cast<const DenseTensorView *>(tensor); - TypedCells cellsRef = view->cellsRef(); - dispatch_1<CallAppendVector>(cellsRef, pos); + dispatch_1<CallAppendVector<OCT> >(view->cellsRef(), pos); } else { *pos++ = value.as_double(); } } +template <typename OCT> const Value &concat_vectors(const Value &a, const Value &b, const vespalib::string &dimension, size_t vector_size, Stash &stash) { - ArrayRef<double> cells = stash.create_array<double>(vector_size); - double *pos = cells.begin(); - append_vector(pos, a); - append_vector(pos, b); + ArrayRef<OCT> cells = stash.create_array<OCT>(vector_size); + OCT *pos = cells.begin(); + append_vector<OCT>(pos, a); + append_vector<OCT>(pos, b); assert(pos == cells.end()); - const ValueType &type = stash.create<ValueType>(ValueType::tensor_type({ValueType::Dimension(dimension, vector_size)})); + const ValueType &type = stash.create<ValueType>(ValueType::tensor_type({ValueType::Dimension(dimension, vector_size)}, ValueType::unify_cell_types(a.type(), b.type()))); return stash.create<DenseTensorView>(type, TypedCells(cells)); } +struct CallConcatVectors { + template <typename OCT> + static const Value &call(const Value &a, const Value &b, const vespalib::string &dimension, size_t vector_size, Stash &stash) { + return concat_vectors<OCT>(a, b, dimension, vector_size, stash); + } +}; + const Value & DefaultTensorEngine::concat(const Value &a, const Value &b, const vespalib::string &dimension, Stash &stash) const { size_t a_size = vector_size(a.type(), dimension); size_t b_size = vector_size(b.type(), dimension); if ((a_size > 0) && (b_size > 0)) { - return concat_vectors(a, b, dimension, a_size + b_size, stash); + CellType result_cell_type = ValueType::unify_cell_types(a.type(), b.type()); + return dispatch_0<CallConcatVectors>(result_cell_type, a, b, dimension, (a_size + b_size), stash); } return to_default(simple_engine().concat(to_simple(a, stash), to_simple(b, stash), dimension, stash), stash); } diff --git a/eval/src/vespa/eval/tensor/dense/dense_add_dimension_optimizer.cpp b/eval/src/vespa/eval/tensor/dense/dense_add_dimension_optimizer.cpp index 842e064de43..a4331b6b251 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_add_dimension_optimizer.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_add_dimension_optimizer.cpp @@ -19,21 +19,8 @@ using namespace eval::operation; namespace { -bool is_concrete_dense_tensor(const ValueType &type) { - if (type.cell_type() != ValueType::CellType::DOUBLE) { - return false; // non-double cell types not supported - } - return type.is_dense(); -} - -bool not_overlapping(const ValueType &a, const ValueType &b) { - size_t npos = ValueType::Dimension::npos; - for (const auto &dim: b.dimensions()) { - if (a.dimension_index(dim.name) != npos) { - return false; - } - } - return true; +bool same_cell_type(const TensorFunction &a, const TensorFunction &b) { + return (a.result_type().cell_type() == b.result_type().cell_type()); } bool is_unit_constant(const TensorFunction &node) { @@ -57,15 +44,14 @@ DenseAddDimensionOptimizer::optimize(const eval::TensorFunction &expr, Stash &st const TensorFunction &lhs = join->lhs(); const TensorFunction &rhs = join->rhs(); if ((join->function() == Mul::f) && - is_concrete_dense_tensor(lhs.result_type()) && - is_concrete_dense_tensor(rhs.result_type()) && - not_overlapping(lhs.result_type(), rhs.result_type())) + lhs.result_type().is_dense() && + rhs.result_type().is_dense()) { - if (is_unit_constant(lhs)) { + if (is_unit_constant(lhs) && same_cell_type(rhs, expr)) { return DenseReplaceTypeFunction::create_compact(expr.result_type(), rhs, stash); } - if (is_unit_constant(rhs)) { - return DenseReplaceTypeFunction::create_compact(expr.result_type(), lhs, stash); + if (is_unit_constant(rhs) && same_cell_type(lhs, expr)) { + return DenseReplaceTypeFunction::create_compact(expr.result_type(), lhs, stash); } } } diff --git a/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp index 9b839e1b12f..8bcaddba3b4 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_dot_product_function.cpp @@ -18,12 +18,6 @@ using namespace eval::operation; namespace { -template <typename T> -ConstArrayRef<T> getCellsRef(const eval::Value &value) { - const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value); - return denseTensor.cellsRef().typify<T>(); -} - template <typename LCT, typename RCT> struct HWSupport { static double call(hwaccelrated::IAccelrated *, const ConstArrayRef<LCT> &lhs, const ConstArrayRef<RCT> &rhs) { @@ -48,8 +42,8 @@ template <> struct HWSupport<double, double> { template <typename LCT, typename RCT> void my_dot_product_op(eval::InterpretedFunction::State &state, uint64_t param) { auto *hw = (hwaccelrated::IAccelrated *)(param); - auto lhs = getCellsRef<LCT>(state.peek(1)); - auto rhs = getCellsRef<RCT>(state.peek(0)); + auto lhs = DenseTensorView::typify_cells<LCT>(state.peek(1)); + auto rhs = DenseTensorView::typify_cells<RCT>(state.peek(0)); double result = HWSupport<LCT,RCT>::call(hw, lhs, rhs); state.pop_pop_push(state.stash.create<eval::DoubleValue>(result)); } diff --git a/eval/src/vespa/eval/tensor/dense/dense_fast_rename_optimizer.cpp b/eval/src/vespa/eval/tensor/dense/dense_fast_rename_optimizer.cpp index d8e1876ac64..ac8442477e4 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_fast_rename_optimizer.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_fast_rename_optimizer.cpp @@ -17,15 +17,10 @@ using namespace eval::tensor_function; namespace { -bool is_concrete_dense_stable_rename(const ValueType &from_type, const ValueType &to_type, - const std::vector<vespalib::string> &from, - const std::vector<vespalib::string> &to) +bool is_dense_stable_rename(const ValueType &from_type, const ValueType &to_type, + const std::vector<vespalib::string> &from, + const std::vector<vespalib::string> &to) { - if (from_type.cell_type() != ValueType::CellType::DOUBLE || - to_type.cell_type() != ValueType::CellType::DOUBLE) - { - return false; // non-double cell types not supported - } if (!from_type.is_dense() || !to_type.is_dense() || (from.size() != to.size())) @@ -51,7 +46,8 @@ DenseFastRenameOptimizer::optimize(const eval::TensorFunction &expr, Stash &stas if (auto rename = as<Rename>(expr)) { const ValueType &from_type = rename->child().result_type(); const ValueType &to_type = expr.result_type(); - if (is_concrete_dense_stable_rename(from_type, to_type, rename->from(), rename->to())) { + if (is_dense_stable_rename(from_type, to_type, rename->from(), rename->to())) { + assert(to_type.cell_type() == from_type.cell_type()); return DenseReplaceTypeFunction::create_compact(to_type, rename->child(), stash); } } diff --git a/eval/src/vespa/eval/tensor/dense/dense_generic_join.hpp b/eval/src/vespa/eval/tensor/dense/dense_generic_join.hpp index aa08e6982bb..cdc89b30fff 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_generic_join.hpp +++ b/eval/src/vespa/eval/tensor/dense/dense_generic_join.hpp @@ -43,7 +43,7 @@ struct CallGenericJoin { DenseDimensionCombiner & combiner, Function &&func) { - using OCT = typename OutputCellType<LCT, RCT>::output_type; + using OCT = typename eval::UnifyCellTypes<LCT, RCT>::type; TypedDenseTensorBuilder<OCT> builder(combiner.result_type); return generic_join(combiner, builder, lhsArr, rhsArr, std::move(func)); } diff --git a/eval/src/vespa/eval/tensor/dense/dense_inplace_join_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_inplace_join_function.cpp index 5fdfdbc4e9f..0b5bba88d37 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_inplace_join_function.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_inplace_join_function.cpp @@ -17,35 +17,45 @@ using namespace eval::tensor_function; namespace { -TypedCells getCellsRef(const eval::Value &value) { - const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value); - return denseTensor.cellsRef(); +template <typename LCT, typename RCT> +void my_inplace_join_left_op(eval::InterpretedFunction::State &state, uint64_t param) { + join_fun_t function = (join_fun_t)param; + auto lhs_cells = unconstify(DenseTensorView::typify_cells<LCT>(state.peek(1))); + auto rhs_cells = DenseTensorView::typify_cells<RCT>(state.peek(0)); + for (size_t i = 0; i < lhs_cells.size(); ++i) { + lhs_cells[i] = function(lhs_cells[i], rhs_cells[i]); + } + state.stack.pop_back(); } -template <bool write_left> -void my_inplace_join_op(eval::InterpretedFunction::State &state, uint64_t param) { +template <typename LCT, typename RCT> +void my_inplace_join_right_op(eval::InterpretedFunction::State &state, uint64_t param) { join_fun_t function = (join_fun_t)param; - ConstArrayRef<double> lhs_cells = getCellsRef(state.peek(1)).typify<double>(); - ConstArrayRef<double> rhs_cells = getCellsRef(state.peek(0)).typify<double>(); - auto dst_cells = unconstify(write_left ? lhs_cells : rhs_cells); - for (size_t i = 0; i < dst_cells.size(); ++i) { - dst_cells[i] = function(lhs_cells[i], rhs_cells[i]); - } - if (write_left) { - state.stack.pop_back(); - } else { - const Value &result = state.stack.back(); - state.pop_pop_push(result); + auto lhs_cells = DenseTensorView::typify_cells<LCT>(state.peek(1)); + auto rhs_cells = unconstify(DenseTensorView::typify_cells<RCT>(state.peek(0))); + for (size_t i = 0; i < rhs_cells.size(); ++i) { + rhs_cells[i] = function(lhs_cells[i], rhs_cells[i]); } + const Value &result = state.stack.back(); + state.pop_pop_push(result); } -bool sameShapeConcreteDenseTensors(const ValueType &a, const ValueType &b) { - if (a.cell_type() != ValueType::CellType::DOUBLE || - b.cell_type() != ValueType::CellType::DOUBLE) - { - return false; // non-double cell types not supported +struct MyInplaceJoinLeftOp { + template <typename LCT, typename RCT> + static auto get_fun() { return my_inplace_join_left_op<LCT,RCT>; } +}; + +struct MyInplaceJoinRightOp { + template <typename LCT, typename RCT> + static auto get_fun() { return my_inplace_join_right_op<LCT,RCT>; } +}; + +eval::InterpretedFunction::op_function my_select(CellType lct, CellType rct, bool write_left) { + if (write_left) { + return select_2<MyInplaceJoinLeftOp>(lct, rct); + } else { + return select_2<MyInplaceJoinRightOp>(lct, rct); } - return (a.is_dense() && (a == b)); } } // namespace vespalib::tensor::<unnamed> @@ -68,7 +78,8 @@ DenseInplaceJoinFunction::~DenseInplaceJoinFunction() eval::InterpretedFunction::Instruction DenseInplaceJoinFunction::compile_self(Stash &) const { - auto op = _write_left ? my_inplace_join_op<true> : my_inplace_join_op<false>; + auto op = my_select(lhs().result_type().cell_type(), + rhs().result_type().cell_type(), _write_left); return eval::InterpretedFunction::Instruction(op, (uint64_t)function()); } @@ -85,11 +96,17 @@ DenseInplaceJoinFunction::optimize(const eval::TensorFunction &expr, Stash &stas if (auto join = as<Join>(expr)) { const TensorFunction &lhs = join->lhs(); const TensorFunction &rhs = join->rhs(); - if ((lhs.result_is_mutable() || rhs.result_is_mutable()) && - sameShapeConcreteDenseTensors(lhs.result_type(), rhs.result_type())) + if (lhs.result_type().is_dense() && + (lhs.result_type().dimensions() == rhs.result_type().dimensions())) { - return stash.create<DenseInplaceJoinFunction>(join->result_type(), lhs, rhs, - join->function(), lhs.result_is_mutable()); + if (lhs.result_is_mutable() && (lhs.result_type() == expr.result_type())) { + return stash.create<DenseInplaceJoinFunction>(join->result_type(), lhs, rhs, + join->function(), /* write left: */ true); + } + if (rhs.result_is_mutable() && (rhs.result_type() == expr.result_type())) { + return stash.create<DenseInplaceJoinFunction>(join->result_type(), lhs, rhs, + join->function(), /* write left: */ false); + } } } return expr; diff --git a/eval/src/vespa/eval/tensor/dense/dense_inplace_map_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_inplace_map_function.cpp index b38a6b175dc..c82cda34a28 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_inplace_map_function.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_inplace_map_function.cpp @@ -16,24 +16,19 @@ using namespace eval::tensor_function; namespace { -ArrayRef<double> getMutableCells(const eval::Value &value) { - const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value); - return unconstify(denseTensor.cellsRef().typify<double>()); -} - +template <typename CT> void my_inplace_map_op(eval::InterpretedFunction::State &state, uint64_t param) { map_fun_t function = (map_fun_t)param; - for (double &cell: getMutableCells(state.peek(0))) { + ArrayRef<CT> cells = unconstify(DenseTensorView::typify_cells<CT>(state.peek(0))); + for (CT &cell: cells) { cell = function(cell); } } -bool isConcreteDenseTensor(const ValueType &type) { - if (type.cell_type() != ValueType::CellType::DOUBLE) { - return false; // non-double cell types not supported - } - return type.is_dense(); -} +struct MyInplaceMapOp { + template <typename CT> + static auto get_fun() { return my_inplace_map_op<CT>; } +}; } // namespace vespalib::tensor::<unnamed> @@ -51,14 +46,16 @@ DenseInplaceMapFunction::~DenseInplaceMapFunction() eval::InterpretedFunction::Instruction DenseInplaceMapFunction::compile_self(Stash &) const { - return eval::InterpretedFunction::Instruction(my_inplace_map_op, (uint64_t)function()); + auto op = select_1<MyInplaceMapOp>(result_type().cell_type()); + return eval::InterpretedFunction::Instruction(op, (uint64_t)function()); } const TensorFunction & DenseInplaceMapFunction::optimize(const eval::TensorFunction &expr, Stash &stash) { if (auto map = as<Map>(expr)) { - if (map->child().result_is_mutable() && isConcreteDenseTensor(map->result_type())) { + if (map->child().result_is_mutable() && map->result_type().is_dense()) { + assert(map->result_type().cell_type() == map->child().result_type().cell_type()); return stash.create<DenseInplaceMapFunction>(map->result_type(), map->child(), map->function()); } } diff --git a/eval/src/vespa/eval/tensor/dense/dense_remove_dimension_optimizer.cpp b/eval/src/vespa/eval/tensor/dense/dense_remove_dimension_optimizer.cpp index 3c58320a6e6..a64d5edbb37 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_remove_dimension_optimizer.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_remove_dimension_optimizer.cpp @@ -14,13 +14,6 @@ using namespace eval::tensor_function; namespace { -bool is_concrete_dense_tensor(const ValueType &type) { - if (type.cell_type() != ValueType::CellType::DOUBLE) { - return false; // non-double cell types not supported - } - return type.is_dense(); -} - bool is_ident_aggr(Aggr aggr) { return ((aggr == Aggr::AVG) || (aggr == Aggr::PROD) || @@ -47,11 +40,12 @@ DenseRemoveDimensionOptimizer::optimize(const eval::TensorFunction &expr, Stash { if (auto reduce = as<Reduce>(expr)) { const TensorFunction &child = reduce->child(); - if (is_concrete_dense_tensor(expr.result_type()) && - is_concrete_dense_tensor(child.result_type()) && + if (expr.result_type().is_dense() && + child.result_type().is_dense() && is_ident_aggr(reduce->aggr()) && is_trivial_dim_list(child.result_type(), reduce->dimensions())) { + assert(expr.result_type().cell_type() == child.result_type().cell_type()); return DenseReplaceTypeFunction::create_compact(expr.result_type(), child, stash); } } diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp index d98cf52d279..3fed84323ca 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.cpp @@ -95,8 +95,7 @@ sameShapeJoin(const ConstArrayRef<LCT> &lhs, const ConstArrayRef<RCT> &rhs, { size_t sz = lhs.size(); assert(sz == rhs.size()); - using OutputSelector = OutputCellType<LCT, RCT>; - using OCT = typename OutputSelector::output_type; + using OCT = typename eval::UnifyCellTypes<LCT,RCT>::type; std::vector<OCT> newCells; newCells.reserve(sz); auto rhsCellItr = rhs.cbegin(); @@ -107,7 +106,7 @@ sameShapeJoin(const ConstArrayRef<LCT> &lhs, const ConstArrayRef<RCT> &rhs, } assert(rhsCellItr == rhs.cend()); assert(newCells.size() == sz); - auto newType = eval::ValueType::tensor_type(lhs_type.dimensions(), OutputSelector::output_cell_type()); + auto newType = eval::ValueType::tensor_type(lhs_type.dimensions(), eval::get_cell_type<OCT>()); return std::make_unique<DenseTensor<OCT>>(std::move(newType), std::move(newCells)); } diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h index 1ec4daf40fd..778f2aa2871 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h +++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_view.h @@ -42,6 +42,13 @@ public: Tensor::UP clone() const override; eval::TensorSpec toSpec() const override; void accept(TensorVisitor &visitor) const override; + + template <typename T> static ConstArrayRef<T> typify_cells(const eval::Value &self) { + return static_cast<const DenseTensorView &>(self).cellsRef().typify<T>(); + } + template <typename T> static ConstArrayRef<T> unsafe_typify_cells(const eval::Value &self) { + return static_cast<const DenseTensorView &>(self).cellsRef().unsafe_typify<T>(); + } protected: explicit DenseTensorView(const eval::ValueType &type_in) : _typeRef(type_in), diff --git a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp index b6ac87ce012..2db5b4e8f92 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp +++ b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.cpp @@ -21,21 +21,36 @@ using namespace eval::operation; namespace { -XWInput getCellsRef(const eval::Value &value) { - const DenseTensorView &denseTensor = static_cast<const DenseTensorView &>(value); - TypedCells ref = denseTensor.cellsRef(); - assert(ref.type == CellType::DOUBLE); - return ref.typify<double>(); -} +template <typename LCT, typename RCT> +struct HWSupport { + static double call(hwaccelrated::IAccelrated *, const LCT *lhs, const RCT *rhs, size_t len) { + double result = 0.0; + for (size_t i = 0; i < len; ++i) { + result += (lhs[i] * rhs[i]); + } + return result; + } +}; +template <> struct HWSupport<float, float> { + static double call(hwaccelrated::IAccelrated *hw, const float *lhs, const float *rhs, size_t len) { + return hw->dotProduct(lhs, rhs, len); + } +}; +template <> struct HWSupport<double, double> { + static double call(hwaccelrated::IAccelrated *hw, const double *lhs, const double *rhs, size_t len) { + return hw->dotProduct(lhs, rhs, len); + } +}; +template <typename LCT, typename RCT, typename OCT> void multiDotProduct(const DenseXWProductFunction::Self &self, - const XWInput &vectorCells, const XWInput &matrixCells, XWOutput &result) + const ConstArrayRef<LCT> &vectorCells, const ConstArrayRef<RCT> &matrixCells, ArrayRef<OCT> &result) { - double *out = result.begin(); - const double *matrixP = matrixCells.cbegin(); - const double * const vectorP = vectorCells.cbegin(); + OCT *out = result.begin(); + const RCT *matrixP = matrixCells.cbegin(); + const LCT * const vectorP = vectorCells.cbegin(); for (size_t row = 0; row < self._resultSize; ++row) { - double cell = self._hwAccelerator->dotProduct(vectorP, matrixP, self._vectorSize); + double cell = HWSupport<LCT,RCT>::call(self._hwAccelerator.get(), vectorP, matrixP, self._vectorSize); *out++ = cell; matrixP += self._vectorSize; } @@ -43,12 +58,13 @@ void multiDotProduct(const DenseXWProductFunction::Self &self, assert(matrixP == matrixCells.cend()); } +template <typename LCT, typename RCT, typename OCT> void transposedProduct(const DenseXWProductFunction::Self &self, - const XWInput &vectorCells, const XWInput &matrixCells, XWOutput &result) + const ConstArrayRef<LCT> &vectorCells, const ConstArrayRef<RCT> &matrixCells, ArrayRef<OCT> &result) { - double *out = result.begin(); - const double * const matrixP = matrixCells.cbegin(); - const double * const vectorP = vectorCells.cbegin(); + OCT *out = result.begin(); + const RCT * const matrixP = matrixCells.cbegin(); + const LCT * const vectorP = vectorCells.cbegin(); for (size_t row = 0; row < self._resultSize; ++row) { double cell = 0; for (size_t col = 0; col < self._vectorSize; ++col) { @@ -59,41 +75,54 @@ void transposedProduct(const DenseXWProductFunction::Self &self, assert(out == result.end()); } -template <bool commonDimensionInnermost> +template <typename LCT, typename RCT, bool commonDimensionInnermost> void my_xw_product_op(eval::InterpretedFunction::State &state, uint64_t param) { DenseXWProductFunction::Self *self = (DenseXWProductFunction::Self *)(param); - XWInput vectorCells = getCellsRef(state.peek(1)); - XWInput matrixCells = getCellsRef(state.peek(0)); - - ArrayRef<double> outputCells = state.stash.create_array<double>(self->_resultSize); + using OCT = typename eval::UnifyCellTypes<LCT,RCT>::type; + auto vectorCells = DenseTensorView::typify_cells<LCT>(state.peek(1)); + auto matrixCells = DenseTensorView::typify_cells<RCT>(state.peek(0)); + auto outputCells = state.stash.create_array<OCT>(self->_resultSize); if (commonDimensionInnermost) { multiDotProduct(*self, vectorCells, matrixCells, outputCells); } else { transposedProduct(*self, vectorCells, matrixCells, outputCells); } + state.pop_pop_push(state.stash.create<DenseTensorView>(self->_resultType, TypedCells(outputCells))); } -bool isConcreteDenseTensor(const ValueType &type, size_t d) { - if (type.cell_type() != ValueType::CellType::DOUBLE) { - return false; // non-double cell types not supported +template <bool common_inner> +struct MyXWProductOp { + template <typename LCT, typename RCT> + static auto get_fun() { return my_xw_product_op<LCT,RCT,common_inner>; } +}; + +eval::InterpretedFunction::op_function my_select(CellType lct, CellType rct, bool common_innermost) { + if (common_innermost) { + return select_2<MyXWProductOp<true> >(lct, rct); + } else { + return select_2<MyXWProductOp<false> >(lct, rct); } +} + +bool isDenseTensor(const ValueType &type, size_t d) { return (type.is_dense() && (type.dimensions().size() == d)); } bool isDenseXWProduct(const ValueType &res, const ValueType &vec, const ValueType &mat) { - if (isConcreteDenseTensor(res, 1) && - isConcreteDenseTensor(vec, 1) && - isConcreteDenseTensor(mat, 2)) + if (isDenseTensor(res, 1) && + isDenseTensor(vec, 1) && + isDenseTensor(mat, 2)) { size_t res_idx = mat.dimension_index(res.dimensions()[0].name); size_t vec_idx = mat.dimension_index(vec.dimensions()[0].name); size_t npos = ValueType::Dimension::npos; if ((res_idx != npos) && (vec_idx != npos) && (res_idx != vec_idx)) { - return ((mat.dimensions()[res_idx].size == res.dimensions()[0].size) && - (mat.dimensions()[vec_idx].size == vec.dimensions()[0].size)); + assert(mat.dimensions()[res_idx].size == res.dimensions()[0].size); + assert(mat.dimensions()[vec_idx].size == vec.dimensions()[0].size); + return true; } } return false; @@ -134,7 +163,8 @@ eval::InterpretedFunction::Instruction DenseXWProductFunction::compile_self(Stash &stash) const { Self &self = stash.create<Self>(result_type(), _vectorSize, _resultSize); - auto op = _commonDimensionInnermost ? my_xw_product_op<true> : my_xw_product_op<false>; + auto op = my_select(lhs().result_type().cell_type(), + rhs().result_type().cell_type(), _commonDimensionInnermost); return eval::InterpretedFunction::Instruction(op, (uint64_t)(&self)); } @@ -150,22 +180,22 @@ DenseXWProductFunction::visit_self(vespalib::ObjectVisitor &visitor) const const TensorFunction & DenseXWProductFunction::optimize(const eval::TensorFunction &expr, Stash &stash) { - const Reduce *reduce = as<Reduce>(expr); - if (reduce && (reduce->aggr() == Aggr::SUM)) { - const ValueType &result_type = reduce->result_type(); - const Join *join = as<Join>(reduce->child()); - if (join && (join->function() == Mul::f)) { - const TensorFunction &lhs = join->lhs(); - const TensorFunction &rhs = join->rhs(); - if (isDenseXWProduct(result_type, lhs.result_type(), rhs.result_type())) { - return createDenseXWProduct(result_type, lhs, rhs, stash); - } - if (isDenseXWProduct(result_type, rhs.result_type(), lhs.result_type())) { - return createDenseXWProduct(result_type, rhs, lhs, stash); - } + const Reduce *reduce = as<Reduce>(expr); + if (reduce && (reduce->aggr() == Aggr::SUM)) { + const ValueType &result_type = reduce->result_type(); + const Join *join = as<Join>(reduce->child()); + if (join && (join->function() == Mul::f)) { + const TensorFunction &lhs = join->lhs(); + const TensorFunction &rhs = join->rhs(); + if (isDenseXWProduct(result_type, lhs.result_type(), rhs.result_type())) { + return createDenseXWProduct(result_type, lhs, rhs, stash); + } + if (isDenseXWProduct(result_type, rhs.result_type(), lhs.result_type())) { + return createDenseXWProduct(result_type, rhs, lhs, stash); } } - return expr; + } + return expr; } } // namespace vespalib::tensor diff --git a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h index 9f1bc12b110..f2f4d67c0f0 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h +++ b/eval/src/vespa/eval/tensor/dense/dense_xw_product_function.h @@ -8,9 +8,6 @@ namespace vespalib::tensor { -using XWInput = ConstArrayRef<double>; -using XWOutput = ArrayRef<double>; - /** * Tensor function for product of one 1-dimensional and one 2-dimensional dense tensor. */ diff --git a/eval/src/vespa/eval/tensor/dense/typed_cells.h b/eval/src/vespa/eval/tensor/dense/typed_cells.h index 98f95d54d9b..0f22c85735e 100644 --- a/eval/src/vespa/eval/tensor/dense/typed_cells.h +++ b/eval/src/vespa/eval/tensor/dense/typed_cells.h @@ -12,25 +12,6 @@ namespace vespalib::tensor { using CellType = vespalib::eval::ValueType::CellType; - -template<typename LCT, typename RCT> struct OutputCellType; -template<> struct OutputCellType<double, double> { - typedef double output_type; - static constexpr CellType output_cell_type() { return CellType::DOUBLE; }; -}; -template<> struct OutputCellType<float, double> { - typedef double output_type; - static constexpr CellType output_cell_type() { return CellType::DOUBLE; }; -}; -template<> struct OutputCellType<double, float> { - typedef double output_type; - static constexpr CellType output_cell_type() { return CellType::DOUBLE; }; -}; -template<> struct OutputCellType<float, float> { - typedef float output_type; - static constexpr CellType output_cell_type() { return CellType::FLOAT; }; -}; - struct TypedCells { const void *data; CellType type; @@ -67,7 +48,7 @@ struct TypedCells { }; template <typename TGT, typename... Args> -auto dispatch_0(CellType ct, Args &&...args) { +decltype(auto) dispatch_0(CellType ct, Args &&...args) { switch (ct) { case CellType::DOUBLE: return TGT::template call<double>(std::forward<Args>(args)...); case CellType::FLOAT: return TGT::template call<float>(std::forward<Args>(args)...); @@ -76,7 +57,7 @@ auto dispatch_0(CellType ct, Args &&...args) { } template <typename TGT, typename... Args> -auto dispatch_1(const TypedCells &a, Args &&...args) { +decltype(auto) dispatch_1(const TypedCells &a, Args &&...args) { switch (a.type) { case CellType::DOUBLE: return TGT::call(a.unsafe_typify<double>(), std::forward<Args>(args)...); case CellType::FLOAT: return TGT::call(a.unsafe_typify<float>(), std::forward<Args>(args)...); @@ -85,7 +66,7 @@ auto dispatch_1(const TypedCells &a, Args &&...args) { } template <typename TGT, typename A1, typename... Args> -auto dispatch_2(A1 &&a, const TypedCells &b, Args &&...args) { +decltype(auto) dispatch_2(A1 &&a, const TypedCells &b, Args &&...args) { switch (b.type) { case CellType::DOUBLE: return dispatch_1<TGT>(std::forward<A1>(a), b.unsafe_typify<double>(), std::forward<Args>(args)...); case CellType::FLOAT: return dispatch_1<TGT>(std::forward<A1>(a), b.unsafe_typify<float>(), std::forward<Args>(args)...); @@ -94,7 +75,7 @@ auto dispatch_2(A1 &&a, const TypedCells &b, Args &&...args) { } template <typename T, typename... Args> -auto select_1(CellType a_type) { +decltype(auto) select_1(CellType a_type) { switch(a_type) { case CellType::DOUBLE: return T::template get_fun<double, Args...>(); case CellType::FLOAT: return T::template get_fun<float, Args...>(); @@ -103,7 +84,7 @@ auto select_1(CellType a_type) { } template <typename T> -auto select_2(CellType a_type, CellType b_type) { +decltype(auto) select_2(CellType a_type, CellType b_type) { switch(b_type) { case CellType::DOUBLE: return select_1<T, double>(a_type); case CellType::FLOAT: return select_1<T, float>(a_type); diff --git a/fastlib/src/vespa/fastlib/io/bufferedfile.cpp b/fastlib/src/vespa/fastlib/io/bufferedfile.cpp index 9af2a73a31b..bd1dd729e39 100644 --- a/fastlib/src/vespa/fastlib/io/bufferedfile.cpp +++ b/fastlib/src/vespa/fastlib/io/bufferedfile.cpp @@ -171,7 +171,7 @@ Fast_BufferedFile::SetPosition(const int64_t s) if ((diff <= 0l) || (diff > (_bufe - buf()))) { const int64_t newPos(s & ~(_buf.size() - 1l) ); if ((s - newPos) >= static_cast<int64_t>(_buf.size())) { - *static_cast<int *>(0) = 1; + abort(); } int64_t oldPos(_filepos); int64_t oldLeft(_fileleft); @@ -181,19 +181,19 @@ Fast_BufferedFile::SetPosition(const int64_t s) fillReadBuf(); if ((oldLeft == _fileleft) && (_fileleft != 0l)) { - *static_cast<int *>(0) = 2; + abort(); } if ((_filepos == oldPos) && (_fileleft != 0l)) { - *static_cast<int *>(0) = 3; + abort(); } if ((_filepos < s) || ((_filepos == s) && (_fileleft != 0))) { - *static_cast<int *>(0) = 4; + abort(); } diff = _filepos - s; if ( !(((diff > 0l) || ((diff == 0l) && (_fileleft == 0l))) && (diff <= static_cast<int64_t>(_buf.size())))) { char tmp[8196]; sprintf(tmp, "diff %" PRId64 " _fileleft=%" PRId64 " _buflen=%zu", diff, _fileleft, _buf.size()); - *static_cast<int *>(0) = 5; + abort(); } } _bufi = _bufe - diff; diff --git a/fbench/README b/fbench/README index 6627786edfb..5d9ee714a32 100644 --- a/fbench/README +++ b/fbench/README @@ -5,24 +5,35 @@ vespa-fbench - fastserver benchmarking program 1 Installing vespa-fbench ------------------------- -The preferred way of running vespa-fbench is to create your own test -directory where you place all fbench executables and prepare test -files. If you have access to the fbench source, you may consult the -'INSTALL' file for information on how to install fbench. If you have a -pre-compiled distribution of fbench, simply extract the archive. The -fbench install directory should contain the following set of files: - - README - bin/vespa-fbench - bin/vespa-fbench-filter-file - bin/vespa-fbench-geturl - bin/plot.pl - bin/pretest.sh - bin/vespa-fbench-result-filter.pl - bin/runtests.sh - bin/separate.pl - bin/vespa-fbench-split-file - +vespa-fbench is distributed together with Vespa in the published RPM +and Docker image. Using the pre-built vespa-fbench is preferred, but +if you have to compile it yourself consult the README.md in +https://github.com/vespa-engine/vespa. + +Installing on Vespa CentOS / RHEL 7: + yum-config-manager --add-repo \ + https://copr.fedorainfracloud.org/coprs/g/vespa/vespa/repo/epel-7/group_vespa-vespa-epel-7.repo + yum -y install epel-release centos-release-scl + yum -y install vespa + +The above installation provides the follwing vespa-fbench executables: + /opt/vespa/bin/vespa-fbench + /opt/vespa/bin/vespa-fbench-filter-file + /opt/vespa/bin/vespa-fbench-geturl + /opt/vespa/bin/vespa-fbench-result-filter.pl + /opt/vespa/bin/vespa-fbench-split-file + +Additional utilities referenced in this document can be fetched from +https://github.com/vespa-engine/vespa/tree/master/fbench/util: + plot.pl + pretest.sh + runtests.sh + separate.pl + +It is also possible to use Docker to directly execute vespa-fbench by +using the pre-built Vespa docker image: + docker run --entrypoint /opt/vespa/bin/vespa-fbench \ + docker.io/vespaengine/vespa <your fbench options> 2 Benchmark overview -------------------- diff --git a/fbench/src/fbench/client.cpp b/fbench/src/fbench/client.cpp index 754fc809511..7ac52ff6c7c 100644 --- a/fbench/src/fbench/client.cpp +++ b/fbench/src/fbench/client.cpp @@ -182,7 +182,7 @@ Client::run() } } if (_output) - _output->write(FBENCH_DELIMITER + 1, strlen(FBENCH_DELIMITER) - 1); + _output->write(&FBENCH_DELIMITER[1], strlen(FBENCH_DELIMITER) - 1); if (_args->_ignoreCount == 0) _masterTimer->Start(); @@ -230,12 +230,12 @@ Client::run() if (!fetch_status.Ok()) { _output->write("\nFBENCH: URL FETCH FAILED!\n", strlen("\nFBENCH: URL FETCH FAILED!\n")); - _output->write(FBENCH_DELIMITER + 1, strlen(FBENCH_DELIMITER) - 1); + _output->write(&FBENCH_DELIMITER[1], strlen(FBENCH_DELIMITER) - 1); } else { sprintf(timestr, "\nTIME USED: %0.4f s\n", _reqTimer->GetTimespan() / 1000.0); _output->write(timestr, strlen(timestr)); - _output->write(FBENCH_DELIMITER + 1, strlen(FBENCH_DELIMITER) - 1); + _output->write(&FBENCH_DELIMITER[1], strlen(FBENCH_DELIMITER) - 1); } } if (fetch_status.ResultSize() >= _args->_byteLimit) { diff --git a/fbench/src/fbench/fbench.cpp b/fbench/src/fbench/fbench.cpp index 205dc867950..723980cd1c7 100644 --- a/fbench/src/fbench/fbench.cpp +++ b/fbench/src/fbench/fbench.cpp @@ -63,13 +63,18 @@ FBench::~FBench() bool FBench::init_crypto_engine(const std::string &ca_certs_file_name, const std::string &cert_chain_file_name, - const std::string &private_key_file_name) + const std::string &private_key_file_name, + bool allow_default_tls) { if (ca_certs_file_name.empty() && cert_chain_file_name.empty() && private_key_file_name.empty()) { - _crypto_engine = std::make_shared<vespalib::NullCryptoEngine>(); + if (allow_default_tls) { + _crypto_engine = vespalib::CryptoEngine::get_default(); + } else { + _crypto_engine = std::make_shared<vespalib::NullCryptoEngine>(); + } return true; } if (ca_certs_file_name.empty()) { @@ -297,7 +302,8 @@ FBench::Usage() printf(" -z : use single query file to be distributed between clients.\n"); printf(" -T <str> : CA certificate file to verify peer against.\n"); printf(" -C <str> : client certificate file name.\n"); - printf(" -K <str> : client private key file name.\n\n"); + printf(" -K <str> : client private key file name.\n"); + printf(" -D : use TLS configuration from environment if T/C/K is not used\n\n"); printf(" <hostname> : the host you want to benchmark.\n"); printf(" <port> : the port to use when contacting the host.\n\n"); printf("Several hostnames and ports can be listed\n"); @@ -332,6 +338,7 @@ FBench::Main(int argc, char *argv[]) std::string ca_certs_file_name; // -T std::string cert_chain_file_name; // -C std::string private_key_file_name; // -K + bool allow_default_tls = false; // -D int restartLimit = -1; bool keepAlive = true; @@ -351,7 +358,7 @@ FBench::Main(int argc, char *argv[]) idx = 1; optError = false; - while((opt = GetOpt(argc, argv, "H:A:T:C:K:a:n:c:l:i:s:q:o:r:m:p:kxyzP", arg, idx)) != -1) { + while((opt = GetOpt(argc, argv, "H:A:T:C:K:Da:n:c:l:i:s:q:o:r:m:p:kxyzP", arg, idx)) != -1) { switch(opt) { case 'A': authority = arg; @@ -372,6 +379,9 @@ FBench::Main(int argc, char *argv[]) case 'K': private_key_file_name = std::string(arg); break; + case 'D': + allow_default_tls = true; + break; case 'a': queryStringToAppend = std::string(arg); break; @@ -443,7 +453,7 @@ FBench::Main(int argc, char *argv[]) return -1; } - if (!init_crypto_engine(ca_certs_file_name, cert_chain_file_name, private_key_file_name)) { + if (!init_crypto_engine(ca_certs_file_name, cert_chain_file_name, private_key_file_name, allow_default_tls)) { fprintf(stderr, "failed to initialize crypto engine\n"); return -1; } diff --git a/fbench/src/fbench/fbench.h b/fbench/src/fbench/fbench.h index 8cbab2e6d6c..e4a8e4e0b27 100644 --- a/fbench/src/fbench/fbench.h +++ b/fbench/src/fbench/fbench.h @@ -35,7 +35,8 @@ private: bool init_crypto_engine(const std::string &ca_certs_file_name, const std::string &cert_chain_file_name, - const std::string &private_key_file_name); + const std::string &private_key_file_name, + bool allow_default_tls); void InitBenchmark(int numClients, int ignoreCount, int cycle, const char *filenamePattern, const char *outputPattern, diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java index 60a5e25b3e0..c4487252e27 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java @@ -193,15 +193,9 @@ public class FileReceiver { } catch (FileAlreadyExistsException e) { // Don't fail if it already exists (we might get the file from several config servers when retrying, servers are down etc. // so it might be written already). Delete temp file/dir in that case, to avoid filling the disk. - log.log(LogLevel.DEBUG, () -> "File '" + destination.getAbsolutePath() + "' already exists, continuing: " + e.getMessage()); - try { - if (tempFile.isDirectory()) - IOUtils.recursiveDeleteDir(tempFile); - else - Files.delete(tempFile.toPath()); - } catch (IOException ioe) { - log.log(LogLevel.WARNING, "Failed deleting file/dir " + tempFile); - } + log.log(LogLevel.INFO, "Failed moving file '" + tempFile.getAbsolutePath() + "' to '" + destination.getAbsolutePath() + + "', '" + destination.getAbsolutePath() + "' already exists"); + deleteFileOrDirectory(tempFile); } catch (IOException e) { String message = "Failed moving file '" + tempFile.getAbsolutePath() + "' to '" + destination.getAbsolutePath() + "'"; log.log(LogLevel.ERROR, message, e); @@ -209,6 +203,18 @@ public class FileReceiver { } } + private static void deleteFileOrDirectory(File path) { + if ( ! path.exists()) return; + try { + if (path.isDirectory()) + IOUtils.recursiveDeleteDir(path); + else + Files.delete(path.toPath()); + } catch (IOException ioe) { + log.log(LogLevel.WARNING, "Failed deleting file/dir " + path); + } + } + private void receiveFileMeta(Request req) { log.log(LogLevel.DEBUG, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters()); FileReference reference = new FileReference(req.parameters().get(0).asString()); diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 0981d7b84e2..3fdd6d8dc48 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -135,7 +135,7 @@ public class Flags { HOSTNAME); public static final UnboundStringFlag CONFIGSERVER_RPC_AUTHORIZER = defineStringFlag( - "configserver-rpc-authorizer", "log-only", + "configserver-rpc-authorizer", "enforce", "Configserver RPC authorizer. Allowed values: ['disable', 'log-only', 'enforce']", "Takes effect on restart of configserver"); @@ -151,12 +151,11 @@ public class Flags { "Takes effect on deployment through controller", APPLICATION_ID); - public static final UnboundBooleanFlag DISABLE_CHEF = defineFeatureFlag( - "disable-chef", false, - "Stops and disables chef-client", - "Takes effect on next host-admin tick", - HOSTNAME, NODE_TYPE); - + public static final UnboundBooleanFlag ENABLE_GROUPING_SESSION_CACHE = defineFeatureFlag( + "enable-grouping-session-cache", false, + "Enable grouping session cache", + "Takes effect at redeployment", + APPLICATION_ID); /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description, diff --git a/fnet/src/tests/frt/method_pt/method_pt.cpp b/fnet/src/tests/frt/method_pt/method_pt.cpp index 608a435bd1d..53960d73466 100644 --- a/fnet/src/tests/frt/method_pt/method_pt.cpp +++ b/fnet/src/tests/frt/method_pt/method_pt.cpp @@ -61,12 +61,18 @@ public: //------------------------------------------------------------- +#ifdef __clang__ +#define UNUSED_MEMBER [[maybe_unused]] +#else +#define UNUSED_MEMBER +#endif + class ComplexA { private: - uint32_t _fill1; - uint32_t _fill2; - uint32_t _fill3; + UNUSED_MEMBER uint32_t _fill1; + UNUSED_MEMBER uint32_t _fill2; + UNUSED_MEMBER uint32_t _fill3; public: @@ -83,9 +89,9 @@ public: class ComplexB { private: - uint32_t _fill1; - uint32_t _fill2; - uint32_t _fill3; + UNUSED_MEMBER uint32_t _fill1; + UNUSED_MEMBER uint32_t _fill2; + UNUSED_MEMBER uint32_t _fill3; public: diff --git a/functions.cmake b/functions.cmake index eb29a9ffe22..a049276d845 100644 --- a/functions.cmake +++ b/functions.cmake @@ -132,7 +132,7 @@ function(vespa_generate_config TARGET RELATIVE_CONFIG_DEF_PATH) add_custom_command( OUTPUT ${CONFIG_H_PATH} ${CONFIG_CPP_PATH} - COMMAND java -Dconfig.spec=${CONFIG_DEF_PATH} -Dconfig.dest=${CONFIG_DEST_PARENT_DIR} -Dconfig.lang=cppng -Dconfig.requireNamespace=false -Dconfig.subdir=${CONFIG_DEST_DIRNAME} -Dconfig.dumpTree=false -Xms64m -Xmx64m -jar ${PROJECT_SOURCE_DIR}/configgen/target/configgen.jar + COMMAND java -Dconfig.spec=${CONFIG_DEF_PATH} -Dconfig.dest=${CONFIG_DEST_PARENT_DIR} -Dconfig.lang=cpp -Dconfig.subdir=${CONFIG_DEST_DIRNAME} -Dconfig.dumpTree=false -Xms64m -Xmx64m -jar ${PROJECT_SOURCE_DIR}/configgen/target/configgen.jar WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.. MAIN_DEPENDENCY ${CONFIG_DEF_PATH} ) diff --git a/http-utils/src/main/java/ai/vespa/util/http/VespaHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/VespaHttpClientBuilder.java index 5e7a9441fc8..529cfdc2aff 100644 --- a/http-utils/src/main/java/ai/vespa/util/http/VespaHttpClientBuilder.java +++ b/http-utils/src/main/java/ai/vespa/util/http/VespaHttpClientBuilder.java @@ -4,24 +4,28 @@ package ai.vespa.util.http; import com.yahoo.security.tls.MixedMode; import com.yahoo.security.tls.TlsContext; import com.yahoo.security.tls.TransportSecurityUtils; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.utils.URIBuilder; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.UnsupportedSchemeException; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.BasicHttpClientConnectionManager; +import org.apache.http.impl.conn.DefaultSchemePortResolver; import org.apache.http.protocol.HttpContext; import javax.net.ssl.SSLParameters; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.InetAddress; import java.util.logging.Level; import java.util.logging.Logger; @@ -69,7 +73,7 @@ public class VespaHttpClientBuilder { private static HttpClientBuilder createBuilder(ConnectionManagerFactory connectionManagerFactory) { var builder = HttpClientBuilder.create(); addSslSocketFactory(builder, connectionManagerFactory); - addTlsAwareRequestInterceptor(builder); + addHttpsRewritingRoutePlanner(builder); return builder; } @@ -83,14 +87,17 @@ public class VespaHttpClientBuilder { } else { builder.setSSLSocketFactory(socketFactory); } + // Workaround that allows re-using https connections, see https://stackoverflow.com/a/42112034/1615280 for details. + // Proper solution would be to add a request interceptor that adds a x500 principal as user token, + // but certificate subject CN is not accessible through the TlsContext currently. + builder.setUserTokenHandler(context -> null); }); } - private static void addTlsAwareRequestInterceptor(HttpClientBuilder builder) { + private static void addHttpsRewritingRoutePlanner(HttpClientBuilder builder) { if (TransportSecurityUtils.isTransportSecurityEnabled() && TransportSecurityUtils.getInsecureMixedMode() != MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER) { - log.log(Level.FINE, "Adding request interceptor to client"); - builder.addInterceptorFirst(new HttpToHttpsRewritingRequestInterceptor()); + builder.setRoutePlanner(new HttpToHttpsRoutePlanner()); } } @@ -106,29 +113,32 @@ public class VespaHttpClientBuilder { .build(); } - static class HttpToHttpsRewritingRequestInterceptor implements HttpRequestInterceptor { + + /** + * Reroutes requests using 'http' to 'https'. + * Implementation inspired by {@link org.apache.http.impl.conn.DefaultRoutePlanner}, but without proxy support. + */ + static class HttpToHttpsRoutePlanner implements HttpRoutePlanner { + @Override - public void process(HttpRequest request, HttpContext context) { - if (request instanceof HttpRequestBase) { - HttpRequestBase httpUriRequest = (HttpRequestBase) request; - httpUriRequest.setURI(rewriteUri(httpUriRequest.getURI())); - } else { - log.log(Level.FINE, () -> "Not a HttpRequestBase - skipping URI rewriting: " + request.getClass().getName()); - } + public HttpRoute determineRoute(HttpHost host, HttpRequest request, HttpContext context) throws HttpException { + HttpClientContext clientContext = HttpClientContext.adapt(context); + RequestConfig config = clientContext.getRequestConfig(); + InetAddress local = config.getLocalAddress(); + + HttpHost target = resolveTarget(host); + boolean secure = target.getSchemeName().equalsIgnoreCase("https"); + return new HttpRoute(target, local, secure); } - private static URI rewriteUri(URI originalUri) { - if (!originalUri.getScheme().equals("http")) { - return originalUri; - } - int port = originalUri.getPort(); - int rewrittenPort = port != -1 ? port : 80; + private HttpHost resolveTarget(HttpHost host) throws HttpException { try { - URI rewrittenUri = new URIBuilder(originalUri).setScheme("https").setPort(rewrittenPort).build(); - log.log(Level.FINE, () -> String.format("Uri rewritten from '%s' to '%s'", originalUri, rewrittenUri)); - return rewrittenUri; - } catch (URISyntaxException e) { - throw new RuntimeException(e); + String originalScheme = host.getSchemeName(); + String scheme = originalScheme.equalsIgnoreCase("http") ? "https" : originalScheme; + int port = DefaultSchemePortResolver.INSTANCE.resolve(host); + return new HttpHost(host.getHostName(), port, scheme); + } catch (UnsupportedSchemeException e) { + throw new HttpException(e.getMessage(), e); } } } diff --git a/http-utils/src/test/java/ai/vespa/util/http/VespaHttpClientBuilderTest.java b/http-utils/src/test/java/ai/vespa/util/http/VespaHttpClientBuilderTest.java index 7ffd0e459b0..85ee0913c58 100644 --- a/http-utils/src/test/java/ai/vespa/util/http/VespaHttpClientBuilderTest.java +++ b/http-utils/src/test/java/ai/vespa/util/http/VespaHttpClientBuilderTest.java @@ -1,14 +1,15 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.util.http; -import ai.vespa.util.http.VespaHttpClientBuilder.HttpToHttpsRewritingRequestInterceptor; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.conn.routing.HttpRoute; import org.junit.Test; -import java.net.URI; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; /** * @author bjorncs @@ -16,24 +17,25 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class VespaHttpClientBuilderTest { @Test - public void request_interceptor_modifies_scheme_of_requests() { - verifyProcessedUriMatchesExpectedOutput("http://dummyhostname:8080/a/path/to/resource?query=value", - "https://dummyhostname:8080/a/path/to/resource?query=value"); + public void route_planner_modifies_scheme_of_requests() throws HttpException { + verifyProcessedUriMatchesExpectedOutput("http://dummyhostname:8080", "https://dummyhostname:8080"); + } + + @Test + public void route_planer_handles_implicit_http_port() throws HttpException { + verifyProcessedUriMatchesExpectedOutput("http://dummyhostname", "https://dummyhostname:80"); } @Test - public void request_interceptor_add_handles_implicit_http_port() { - verifyProcessedUriMatchesExpectedOutput("http://dummyhostname/a/path/to/resource?query=value", - "https://dummyhostname:80/a/path/to/resource?query=value"); + public void route_planer_handles_https_port() throws HttpException { + verifyProcessedUriMatchesExpectedOutput("http://dummyhostname:443", "https://dummyhostname:443"); } - private static void verifyProcessedUriMatchesExpectedOutput(String inputUri, String expectedOutputUri) { - var interceptor = new HttpToHttpsRewritingRequestInterceptor(); - HttpGet request = new HttpGet(inputUri); - interceptor.process(request, new BasicHttpContext()); - URI modifiedUri = request.getURI(); - URI expectedUri = URI.create(expectedOutputUri); - assertThat(modifiedUri).isEqualTo(expectedUri); + private static void verifyProcessedUriMatchesExpectedOutput(String inputHostString, String expectedHostString) throws HttpException { + var routePlanner = new VespaHttpClientBuilder.HttpToHttpsRoutePlanner(); + HttpRoute newRoute = routePlanner.determineRoute(HttpHost.create(inputHostString), mock(HttpRequest.class), new HttpClientContext()); + HttpHost target = newRoute.getTargetHost(); + assertEquals(expectedHostString, target.toURI()); } }
\ No newline at end of file diff --git a/jaxrs_client_utils/pom.xml b/jaxrs_client_utils/pom.xml index 636fbab7bb0..d32d4c5eccc 100644 --- a/jaxrs_client_utils/pom.xml +++ b/jaxrs_client_utils/pom.xml @@ -16,6 +16,7 @@ <packaging>container-plugin</packaging> <name>${project.artifactId}</name> <dependencies> + <!-- provided --> <dependency> <groupId>com.yahoo.vespa</groupId> <artifactId>vespajlib</artifactId> @@ -29,6 +30,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>security-utils</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> <version>2.0</version> @@ -44,6 +51,8 @@ <artifactId>jersey-proxy-client</artifactId> <scope>provided</scope> </dependency> + + <!-- test --> <dependency> <groupId>com.yahoo.vespa</groupId> <artifactId>jaxrs_utils</artifactId> diff --git a/jaxrs_client_utils/src/main/java/ai/vespa/util/http/VespaClientBuilderFactory.java b/jaxrs_client_utils/src/main/java/ai/vespa/util/http/VespaClientBuilderFactory.java new file mode 100644 index 00000000000..d55128069c4 --- /dev/null +++ b/jaxrs_client_utils/src/main/java/ai/vespa/util/http/VespaClientBuilderFactory.java @@ -0,0 +1,72 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.util.http; + +import com.yahoo.security.tls.MixedMode; +import com.yahoo.security.tls.TlsContext; +import com.yahoo.security.tls.TransportSecurityUtils; + +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Factory for JAX-RS http client builder for internal Vespa communications over http/https. + * + * Notes: + * - hostname verification is not enabled - CN/SAN verification is assumed to be handled by the underlying x509 trust manager. + * - ssl context or hostname verifier must not be overriden by the caller + * + * @author bjorncs + */ +public class VespaClientBuilderFactory implements AutoCloseable { + + private static final Logger log = Logger.getLogger(VespaClientBuilderFactory.class.getName()); + + private final TlsContext tlsContext = TransportSecurityUtils.createTlsContext().orElse(null); + private final MixedMode mixedMode = TransportSecurityUtils.getInsecureMixedMode(); + + public ClientBuilder newBuilder() { + ClientBuilder builder = ClientBuilder.newBuilder(); + setSslConfiguration(builder); + return builder; + } + + private void setSslConfiguration(ClientBuilder builder) { + if (tlsContext != null) { + builder.sslContext(tlsContext.context()); + builder.hostnameVerifier((hostname, sslSession) -> true); // disable hostname verification + if (mixedMode != MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER) { + builder.register(new UriRewritingRequestFilter()); + } + } + } + + @Override + public void close() { + if (tlsContext != null) { + tlsContext.close(); + } + } + + static class UriRewritingRequestFilter implements ClientRequestFilter { + @Override + public void filter(ClientRequestContext requestContext) { + requestContext.setUri(rewriteUri(requestContext.getUri())); + } + + private static URI rewriteUri(URI originalUri) { + if (!originalUri.getScheme().equals("http")) { + return originalUri; + } + int port = originalUri.getPort(); + int rewrittenPort = port != -1 ? port : 80; + URI rewrittenUri = UriBuilder.fromUri(originalUri).scheme("https").port(rewrittenPort).build(); + log.log(Level.FINE, () -> String.format("Uri rewritten from '%s' to '%s'", originalUri, rewrittenUri)); + return rewrittenUri; + } + } +} diff --git a/jaxrs_client_utils/src/main/java/ai/vespa/util/http/package-info.java b/jaxrs_client_utils/src/main/java/ai/vespa/util/http/package-info.java new file mode 100644 index 00000000000..8ee304d6de8 --- /dev/null +++ b/jaxrs_client_utils/src/main/java/ai/vespa/util/http/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package ai.vespa.util.http; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/VespaJerseyJaxRsClientFactory.java b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/VespaJerseyJaxRsClientFactory.java new file mode 100644 index 00000000000..bdc89d737d4 --- /dev/null +++ b/jaxrs_client_utils/src/main/java/com/yahoo/vespa/jaxrs/client/VespaJerseyJaxRsClientFactory.java @@ -0,0 +1,55 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.jaxrs.client; + +import ai.vespa.util.http.VespaClientBuilderFactory; +import com.yahoo.vespa.applicationmodel.HostName; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.proxy.WebResourceFactory; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriBuilder; +import java.util.List; + +/** + * Factory for creating Jersey based Vespa clients from a JAX-RS resource interface. + * + * @author bjorncs + */ +public class VespaJerseyJaxRsClientFactory implements JaxRsClientFactory, AutoCloseable { + + private final VespaClientBuilderFactory clientBuilder = new VespaClientBuilderFactory(); + // Client is a heavy-weight object with a finalizer so we create only one and re-use it + private final Client client; + + public VespaJerseyJaxRsClientFactory(String userAgent) { + this.client = clientBuilder.newBuilder() + .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true) // Allow empty PUT + .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) // Allow e.g. PATCH method. + .property(ClientProperties.FOLLOW_REDIRECTS, true) + .register((ClientRequestFilter) context -> context.getHeaders().put(HttpHeaders.USER_AGENT, List.of(userAgent))) + .build(); + } + + @Override + public <T> T createClient(Params<T> params) { + WebTarget target = client.target(params.uri()); + target.property(ClientProperties.CONNECT_TIMEOUT, (int) params.connectTimeout().toMillis()); + target.property(ClientProperties.READ_TIMEOUT, (int) params.readTimeout().toMillis()); + return WebResourceFactory.newResource(params.apiClass(), target); + } + + @Override + public <T> T createClient(Class<T> apiClass, HostName hostName, int port, String pathPrefix, String scheme) { + UriBuilder uriBuilder = UriBuilder.fromPath(pathPrefix).host(hostName.s()).port(port).scheme(scheme); + return createClient(new Params<>(apiClass, uriBuilder.build())); + } + + @Override + public void close() { + clientBuilder.close(); + } +} diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java index e28809c2928..d4a62bb179f 100755 --- a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingNode.java @@ -244,7 +244,7 @@ public class RoutingNode implements ReplyHandler { policy.merge(routingContext); } catch (RuntimeException e) { setError(ErrorCode.POLICY_ERROR, - "Policy '" + dir.getName() + "' threw an exception during merge; " + exceptionMessageWithTrace(e)); + "Policy '" + dir.getName() + "' and route '" + route + "' threw an exception during merge; " + exceptionMessageWithTrace(e)); } if (reply == null) { setError(ErrorCode.APP_FATAL_ERROR, @@ -539,7 +539,7 @@ public class RoutingNode implements ReplyHandler { policy.select(routingContext); } catch (RuntimeException e) { setError(ErrorCode.POLICY_ERROR, - "Policy '" + dir.getName() + "' threw an exception during select; " + exceptionMessageWithTrace(e)); + "Policy '" + dir.getName() + "' and route '" +route + "' threw an exception during select; " + exceptionMessageWithTrace(e)); return false; } if (children.isEmpty()) { diff --git a/messagebus/src/vespa/messagebus/message.h b/messagebus/src/vespa/messagebus/message.h index e7e7d74033e..539720374d0 100644 --- a/messagebus/src/vespa/messagebus/message.h +++ b/messagebus/src/vespa/messagebus/message.h @@ -29,8 +29,10 @@ public: * Constructs a new instance of this class. */ Message(); - Message(Message &&) = default; - Message & operator = (Message &&) = default; + Message(const Message &) = delete; + Message(Message &&) = delete; + Message & operator = (const Message &) = delete; + Message & operator = (Message &&) = delete; /** * If a message is deleted with elements on the callstack, this destructor diff --git a/metrics/src/vespa/metrics/metric.cpp b/metrics/src/vespa/metrics/metric.cpp index 579e3bdfbe3..20083300271 100644 --- a/metrics/src/vespa/metrics/metric.cpp +++ b/metrics/src/vespa/metrics/metric.cpp @@ -12,6 +12,7 @@ #include <iterator> #include <cassert> #include <algorithm> +#include <ostream> namespace metrics { diff --git a/metrics/src/vespa/metrics/xmlwriter.cpp b/metrics/src/vespa/metrics/xmlwriter.cpp index 25bc4e23d96..70e4f72d761 100644 --- a/metrics/src/vespa/metrics/xmlwriter.cpp +++ b/metrics/src/vespa/metrics/xmlwriter.cpp @@ -37,7 +37,7 @@ XmlWriter::visitMetricSet(const MetricSet& set, bool) { using namespace vespalib::xml; if (set.used() || _verbosity >= 2) { - _xos << XmlTag(set.getName(), CONVERT_ILLEGAL_CHARACTERS); + _xos << XmlTag(set.getName(), XmlTagFlags::CONVERT_ILLEGAL_CHARACTERS); printCommonXmlParts(set); return true; } @@ -56,7 +56,7 @@ XmlWriter::visitCountMetric(const AbstractCountMetric& metric, bool) if (!metric.inUse(*values) && _verbosity < 2) return true; using namespace vespalib::xml; std::ostringstream ost; - _xos << XmlTag(metric.getName(), CONVERT_ILLEGAL_CHARACTERS) + _xos << XmlTag(metric.getName(), XmlTagFlags::CONVERT_ILLEGAL_CHARACTERS) << XmlAttribute(metric.sumOnAdd() ? "count" : "value", values->toString("count")); printCommonXmlParts(metric); @@ -70,7 +70,7 @@ XmlWriter::visitValueMetric(const AbstractValueMetric& metric, bool) MetricValueClass::UP values(metric.getValues()); if (!metric.inUse(*values) && _verbosity < 2) return true; using namespace vespalib::xml; - _xos << XmlTag(metric.getName(), CONVERT_ILLEGAL_CHARACTERS) + _xos << XmlTag(metric.getName(), XmlTagFlags::CONVERT_ILLEGAL_CHARACTERS) << XmlAttribute("average", values->getLongValue("count") == 0 ? 0 : values->getDoubleValue("total") / values->getDoubleValue("count")) diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/DimensionRenamer.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/DimensionRenamer.java index 0f563a75b11..e9be35b6f84 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/DimensionRenamer.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/DimensionRenamer.java @@ -75,7 +75,6 @@ public class DimensionRenamer { if (solution != null) return solution; for (RenameTarget target : prioritizedRenameTargets()) { - System.out.println("Trying rename " + target); target.insertRename(this); solution = solveWithOrWithoutSoftConstraints(maxIterations); if (solution != null) return solution; @@ -90,8 +89,9 @@ public class DimensionRenamer { if ( solution == null) { ListMap<Arc, Constraint> hardConstraints = new ListMap<>(); boolean anyRemoved = copyHard(constraints, hardConstraints); - if (anyRemoved) + if (anyRemoved) { solution = NamingConstraintSolver.solve(dimensions, hardConstraints, maxIterations); + } } return solution; } diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/Const.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/Const.java index d13c1ad5f3c..fc59ad35ef8 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/Const.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/Const.java @@ -10,6 +10,8 @@ import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; import com.yahoo.searchlib.rankingexpression.rule.TensorFunctionNode; import com.yahoo.tensor.TensorType; +import com.yahoo.tensor.evaluation.VariableTensor; +import com.yahoo.tensor.functions.Rename; import com.yahoo.tensor.functions.TensorFunction; import java.util.List; @@ -18,6 +20,7 @@ import java.util.Optional; public class Const extends IntermediateOperation { private final AttributeMap attributeMap; + private OrderedTensorType standardNamingType; // using standard naming convention: d0, d1, ... public Const(String modelName, String nodeName, @@ -27,6 +30,7 @@ public class Const extends IntermediateOperation { super(modelName, nodeName, inputs); this.attributeMap = attributeMap; this.type = type.rename(vespaName() + "_"); + standardNamingType = OrderedTensorType.standardType(type); setConstantValue(value()); } @@ -51,7 +55,13 @@ public class Const extends IntermediateOperation { } else { expressionNode = new ReferenceNode(Reference.simple("constant", vespaName())); } - return new TensorFunctionNode.TensorFunctionExpressionNode(expressionNode); + TensorFunction output = new TensorFunctionNode.TensorFunctionExpressionNode(expressionNode); + if ( ! standardNamingType.equals(type)) { + List<String> renameFrom = standardNamingType.dimensionNames(); + List<String> renameTo = type.dimensionNames(); + output = new Rename(output, renameFrom, renameTo); + } + return output; } /** Constant names are prefixed by "modelName_" to avoid name conflicts between models */ diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java index c3980b8fe93..9c9fed89585 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/operations/IntermediateOperation.java @@ -102,12 +102,12 @@ public abstract class IntermediateOperation { /** Add dimension name constraints for this operation */ public void addDimensionNameConstraints(DimensionRenamer renamer) { } - /** Conveinence method to adds dimensions and constraints of the given tensor type */ + /** Convenience method to adds dimensions and constraints of the given tensor type */ protected void addConstraintsFrom(OrderedTensorType type, DimensionRenamer renamer) { for (int i = 0; i < type.dimensions().size(); i++) { renamer.addDimension(type.dimensions().get(i).name()); - // Each dimension is distinct: + // Each dimension is distinct and ordered correctly: for (int j = i + 1; j < type.dimensions().size(); j++) { renamer.addConstraint(type.dimensions().get(i).name(), type.dimensions().get(j).name(), DimensionRenamer.Constraint.notEqual(false), @@ -216,29 +216,29 @@ public abstract class IntermediateOperation { return i < 0 ? 0 : Integer.parseInt(name.substring(i + 1)); } - /** - * An interface mapping operation attributes to Vespa Values. - * Adapter for differences in different model types. - */ - public interface AttributeMap { - Optional<Value> get(String key); - Optional<Value> get(String key, OrderedTensorType type); - Optional<List<Value>> getList(String key); - } - public abstract String operationName(); @Override public String toString() { - return operationName() + + return operationName() + "(" + inputs().stream().map(input -> asString(input.type())).collect(Collectors.joining(", ")) + ")"; } public String toFullString() { - return "\t" + lazyGetType() + ":\t" + operationName() + + return "\t" + lazyGetType() + ":\t" + operationName() + "(" + inputs().stream().map(input -> input.toFullString()).collect(Collectors.joining(", ")) + ")"; } + /** + * An interface mapping operation attributes to Vespa Values. + * Adapter for differences in different model types. + */ + public interface AttributeMap { + Optional<Value> get(String key); + Optional<Value> get(String key, OrderedTensorType type); + Optional<List<Value>> getList(String key); + } + } diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/GraphImporter.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/GraphImporter.java index 357794faee2..0e9c98b2b56 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/GraphImporter.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/GraphImporter.java @@ -34,6 +34,7 @@ import org.tensorflow.framework.MetaGraphDef; import org.tensorflow.framework.NodeDef; import org.tensorflow.framework.SignatureDef; import org.tensorflow.framework.TensorInfo; +import org.tensorflow.op.core.DecodeRaw; import java.io.IOException; import java.util.List; diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/TensorConverter.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/TensorConverter.java index 9cba388d00e..6ab7a69e469 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/TensorConverter.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/tensorflow/TensorConverter.java @@ -5,15 +5,16 @@ import ai.vespa.rankingexpression.importer.OrderedTensorType; import com.yahoo.tensor.IndexedTensor; import com.yahoo.tensor.Tensor; import com.yahoo.tensor.TensorType; -import org.tensorflow.DataType; +import org.tensorflow.framework.DataType; import org.tensorflow.framework.TensorProto; +import org.tensorflow.framework.TensorShapeProto; import java.nio.ByteBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; - +import java.util.List; /** * Converts TensorFlow tensors into Vespa tensors. @@ -48,9 +49,11 @@ public class TensorConverter { static Tensor toVespaTensor(TensorProto tensorProto, TensorType type) { IndexedTensor.BoundBuilder builder = (IndexedTensor.BoundBuilder)Tensor.Builder.of(type); Values values = readValuesOf(tensorProto); - for (int i = 0; i < values.size(); ++i) { + if (values.size() == 0) // Might be stored as "tensor_content" instead + return toVespaTensor(readTensorContentOf(tensorProto)); + + for (int i = 0; i < values.size(); ++i) builder.cellByDirectIndex(i, values.get(i)); - } return builder.build(); } @@ -74,28 +77,47 @@ public class TensorConverter { case UINT8: return new IntValues(tfTensor); case INT32: return new IntValues(tfTensor); case INT64: return new LongValues(tfTensor); + default: throw new IllegalArgumentException("Cannot convert a tensor with elements of type " + + tfTensor.dataType() + " to a Vespa tensor"); } - throw new IllegalArgumentException("Cannot convert a tensor with elements of type " + - tfTensor.dataType() + " to a Vespa tensor"); } private static Values readValuesOf(TensorProto tensorProto) { switch (tensorProto.getDtype()) { - case DT_BOOL: - return new ProtoBoolValues(tensorProto); - case DT_HALF: - return new ProtoHalfValues(tensorProto); - case DT_INT16: - case DT_INT32: - return new ProtoIntValues(tensorProto); - case DT_INT64: - return new ProtoInt64Values(tensorProto); - case DT_FLOAT: - return new ProtoFloatValues(tensorProto); - case DT_DOUBLE: - return new ProtoDoubleValues(tensorProto); - } - throw new IllegalArgumentException("Unsupported data type in attribute tensor import"); + case DT_BOOL: return new ProtoBoolValues(tensorProto); + case DT_HALF: return new ProtoHalfValues(tensorProto); + case DT_INT16: case DT_INT32: return new ProtoIntValues(tensorProto); + case DT_INT64: return new ProtoInt64Values(tensorProto); + case DT_FLOAT: return new ProtoFloatValues(tensorProto); + case DT_DOUBLE: return new ProtoDoubleValues(tensorProto); + default: throw new IllegalArgumentException("Unsupported data type in attribute tensor import"); + } + } + + private static Class dataTypeToClass(DataType dataType) { + switch (dataType) { + case DT_BOOL: return Boolean.class; + case DT_INT16: return Short.class; + case DT_INT32: return Integer.class; + case DT_INT64: return Long.class; + case DT_HALF: return Float.class; + case DT_FLOAT: return Float.class; + case DT_DOUBLE: return Double.class; + default: throw new IllegalArgumentException("Unsupported data type in attribute tensor import"); + } + } + + private static org.tensorflow.Tensor readTensorContentOf(TensorProto tensorProto) { + return org.tensorflow.Tensor.create(dataTypeToClass(tensorProto.getDtype()), + asSizeArray(tensorProto.getTensorShape().getDimList()), + tensorProto.getTensorContent().asReadOnlyByteBuffer()); + } + + private static long[] asSizeArray(List<TensorShapeProto.Dim> dimensions) { + long[] sizes = new long[dimensions.size()]; + for (int i = 0; i < dimensions.size(); i++) + sizes[i] = dimensions.get(i).getSize(); + return sizes; } /** Allows reading values from buffers of various numeric types as bytes */ diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/DropoutImportTestCase.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/DropoutImportTestCase.java index b9d767774be..f38403bfbd4 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/DropoutImportTestCase.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/DropoutImportTestCase.java @@ -34,7 +34,7 @@ public class DropoutImportTestCase { ImportedMlFunction function = signature.outputFunction("y", "y"); assertNotNull(function); - assertEquals("join(join(imported_ml_function_test_outputs_BiasAdd, reduce(constant(test_outputs_Const), sum, d1), f(a,b)(a * b)), imported_ml_function_test_outputs_BiasAdd, f(a,b)(max(a,b)))", + assertEquals("join(join(imported_ml_function_test_outputs_BiasAdd, reduce(rename(constant(test_outputs_Const), d0, d1), sum, d1), f(a,b)(a * b)), imported_ml_function_test_outputs_BiasAdd, f(a,b)(max(a,b)))", function.expression()); model.assertEqualResult("X", "outputs/Maximum"); assertEquals("{X=tensor(d0[],d1[784])}", function.argumentTypes().toString()); diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java new file mode 100644 index 00000000000..46ced6f42ad --- /dev/null +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java @@ -0,0 +1,79 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.rankingexpression.importer.tensorflow; + +import ai.vespa.rankingexpression.importer.ImportedModel; +import com.yahoo.tensor.Tensor; +import com.yahoo.tensor.TensorType; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author bratseth + */ +public class RegressionTestCase { + + @Test + public void testRegressionModel1() { + TestableTensorFlowModel model = new TestableTensorFlowModel("test", + "src/test/models/tensorflow/regression/test1", + 14, + 1536); + + // Check constants + Assert.assertEquals(2, model.get().largeConstants().size()); + + Tensor constant0 = Tensor.from(model.get().largeConstants().get("test_Variable_read")); + assertNotNull(constant0); + assertEquals(new TensorType.Builder().indexed("d2", 1536).indexed("d1", 14).build(), + constant0.type()); + assertEquals(21504, constant0.size()); + + Tensor constant1 = Tensor.from(model.get().largeConstants().get("test_Variable_1_read")); + assertNotNull(constant1); + assertEquals(new TensorType.Builder().indexed("d1", 14).build(), constant1.type()); + assertEquals(14, constant1.size()); + + // Check (provided) functions + Assert.assertEquals(0, model.get().functions().size()); + + // Check signatures + Assert.assertEquals(1, model.get().signatures().size()); + ImportedModel.Signature signature = model.get().signatures().get("serving_default"); + assertNotNull(signature); + + // Test execution + model.assertEqualResult("input", "MatMul"); + model.assertEqualResult("input", "logits"); + model.assertEqualResult("input", "Sigmoid"); + model.assertEqualResult("input", "add"); + } + + @Test + public void testRegressionModel2() { + TestableTensorFlowModel model = new TestableTensorFlowModel("test", + "src/test/models/tensorflow/regression/test2", + 14, + 1536, + false); + + // Check constants + Assert.assertEquals(2, model.get().largeConstants().size()); + + // Check (provided) functions + Assert.assertEquals(0, model.get().functions().size()); + + // Check signatures + Assert.assertEquals(1, model.get().signatures().size()); + ImportedModel.Signature signature = model.get().signatures().get("serving_default"); + assertNotNull(signature); + + // Test execution + model.assertEqualResult("input", "MatMul"); + model.assertEqualResult("input", "add"); + model.assertEqualResult("input", "predict"); + } + +} diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java index 75fa2ed7933..41f343dbdaa 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java @@ -16,6 +16,7 @@ import com.yahoo.tensor.TensorType; import org.tensorflow.SavedModelBundle; import org.tensorflow.Session; +import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.util.List; @@ -23,7 +24,6 @@ import static org.junit.Assert.assertEquals; /** * Helper for TensorFlow import tests: Imports a model and provides asserts on it. - * This currently assumes the TensorFlow model takes a single input of type tensor(d0[1],d1[784]) * * @author bratseth */ @@ -32,19 +32,26 @@ public class TestableTensorFlowModel { private SavedModelBundle tensorFlowModel; private ImportedModel model; - // Sizes of the input vector - private int d0Size = 1; - private int d1Size = 784; + // Spec of the input vector + private final boolean floatInput; // false: double + private final int d0Size; + private final int d1Size; + public TestableTensorFlowModel(String modelName, String modelDir) { - tensorFlowModel = SavedModelBundle.load(modelDir, "serve"); - model = new TensorFlowImporter().importModel(modelName, modelDir, tensorFlowModel); + this(modelName, modelDir, 1, 784); } public TestableTensorFlowModel(String modelName, String modelDir, int d0Size, int d1Size) { - this(modelName, modelDir); + this(modelName, modelDir, d0Size, d1Size, true); + } + + public TestableTensorFlowModel(String modelName, String modelDir, int d0Size, int d1Size, boolean floatInput) { + tensorFlowModel = SavedModelBundle.load(modelDir, "serve"); + model = new TensorFlowImporter().importModel(modelName, modelDir, tensorFlowModel); this.d0Size = d0Size; this.d1Size = d1Size; + this.floatInput = floatInput; } public ImportedModel get() { return model; } @@ -53,7 +60,7 @@ public class TestableTensorFlowModel { public void assertEqualResultSum(String inputName, String operationName, double delta) { Tensor tfResult = tensorFlowExecute(tensorFlowModel, inputName, operationName); Context context = contextFrom(model); - Tensor placeholder = placeholderArgument(); + Tensor placeholder = vespaInputArgument(); context.put(inputName, new TensorValue(placeholder)); model.functions().forEach((k, v) -> evaluateFunction(context, model, k)); @@ -71,8 +78,8 @@ public class TestableTensorFlowModel { public void assertEqualResult(String inputName, String operationName) { Tensor tfResult = tensorFlowExecute(tensorFlowModel, inputName, operationName); Context context = contextFrom(model); - Tensor placeholder = placeholderArgument(); - context.put(inputName, new TensorValue(placeholder)); + Tensor inputValue = vespaInputArgument(); + context.put(inputName, new TensorValue(inputValue)); model.functions().forEach((k, v) -> evaluateFunction(context, model, k)); @@ -81,17 +88,14 @@ public class TestableTensorFlowModel { optimizer.optimize(expression, (ContextIndex)context); Tensor vespaResult = expression.evaluate(context).asTensor(); - assertEquals("Operation '" + operationName + "' produces equal results", tfResult, vespaResult); + assertEquals("Operation '" + operationName + "': Actual value from Vespa equals expected value from TensorFlow", + tfResult, vespaResult); } private Tensor tensorFlowExecute(SavedModelBundle model, String inputName, String operationName) { Session.Runner runner = model.session().runner(); - FloatBuffer fb = FloatBuffer.allocate(d0Size * d1Size); - for (int i = 0; i < d1Size; ++i) { - fb.put(i, (float)(i * 1.0 / d1Size)); - } - org.tensorflow.Tensor<?> placeholder = org.tensorflow.Tensor.create(new long[]{ d0Size, d1Size }, fb); - runner.feed(inputName, placeholder); + org.tensorflow.Tensor<?> input = floatInput ? tensorFlowFloatInputArgument() : tensorFlowDoubleInputArgument(); + runner.feed(inputName, input); List<org.tensorflow.Tensor<?>> results = runner.fetch(operationName).run(); assertEquals(1, results.size()); return TensorConverter.toVespaTensor(results.get(0)); @@ -104,7 +108,28 @@ public class TestableTensorFlowModel { return context; } - private Tensor placeholderArgument() { + /** Must be the same as vespaInputArgument() */ + private org.tensorflow.Tensor<?> tensorFlowDoubleInputArgument() { + DoubleBuffer fb = DoubleBuffer.allocate(d0Size * d1Size); + int i = 0; + for (int d0 = 0; d0 < d0Size; d0++) + for (int d1 = 0; d1 < d1Size; ++d1) + fb.put(i++, (d1 * 1.0 / d1Size)); + return org.tensorflow.Tensor.create(new long[]{ d0Size, d1Size }, fb); + } + + /** Must be the same as vespaInputArgument() */ + private org.tensorflow.Tensor<?> tensorFlowFloatInputArgument() { + FloatBuffer fb = FloatBuffer.allocate(d0Size * d1Size); + int i = 0; + for (int d0 = 0; d0 < d0Size; d0++) + for (int d1 = 0; d1 < d1Size; ++d1) + fb.put(i++, (float)(d1 * 1.0 / d1Size)); + return org.tensorflow.Tensor.create(new long[]{ d0Size, d1Size }, fb); + } + + /** Must be the same as tensorFlowFloatInputArgument() */ + private Tensor vespaInputArgument() { Tensor.Builder b = Tensor.Builder.of(new TensorType.Builder().indexed("d0", d0Size).indexed("d1", d1Size).build()); for (int d0 = 0; d0 < d0Size; d0++) for (int d1 = 0; d1 < d1Size; d1++) diff --git a/model-integration/src/test/models/tensorflow/regression/test1/saved_model.pbtxt b/model-integration/src/test/models/tensorflow/regression/test1/saved_model.pbtxt new file mode 100644 index 00000000000..678fced00d2 --- /dev/null +++ b/model-integration/src/test/models/tensorflow/regression/test1/saved_model.pbtxt @@ -0,0 +1,6172 @@ +saved_model_schema_version: 1 +meta_graphs { + meta_info_def { + stripped_op_list { + op { + name: "Add" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_UINT8 + type: DT_INT8 + type: DT_INT16 + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + type: DT_STRING + } + } + } + } + op { + name: "AddN" + input_arg { + name: "inputs" + type_attr: "T" + number_attr: "N" + } + output_arg { + name: "sum" + type_attr: "T" + } + attr { + name: "N" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + type: DT_VARIANT + } + } + } + is_aggregate: true + is_commutative: true + } + op { + name: "ApplyGradientDescent" + input_arg { + name: "var" + type_attr: "T" + is_ref: true + } + input_arg { + name: "alpha" + type_attr: "T" + } + input_arg { + name: "delta" + type_attr: "T" + } + output_arg { + name: "out" + type_attr: "T" + is_ref: true + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "use_locking" + type: "bool" + default_value { + b: false + } + } + } + op { + name: "Assign" + input_arg { + name: "ref" + type_attr: "T" + is_ref: true + } + input_arg { + name: "value" + type_attr: "T" + } + output_arg { + name: "output_ref" + type_attr: "T" + is_ref: true + } + attr { + name: "T" + type: "type" + } + attr { + name: "validate_shape" + type: "bool" + default_value { + b: true + } + } + attr { + name: "use_locking" + type: "bool" + default_value { + b: true + } + } + allows_uninitialized_input: true + } + op { + name: "BroadcastGradientArgs" + input_arg { + name: "s0" + type_attr: "T" + } + input_arg { + name: "s1" + type_attr: "T" + } + output_arg { + name: "r0" + type_attr: "T" + } + output_arg { + name: "r1" + type_attr: "T" + } + attr { + name: "T" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "Cast" + input_arg { + name: "x" + type_attr: "SrcT" + } + output_arg { + name: "y" + type_attr: "DstT" + } + attr { + name: "SrcT" + type: "type" + } + attr { + name: "DstT" + type: "type" + } + attr { + name: "Truncate" + type: "bool" + default_value { + b: false + } + } + } + op { + name: "Const" + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "value" + type: "tensor" + } + attr { + name: "dtype" + type: "type" + } + } + op { + name: "Exp" + input_arg { + name: "x" + type_attr: "T" + } + output_arg { + name: "y" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "Fill" + input_arg { + name: "dims" + type_attr: "index_type" + } + input_arg { + name: "value" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + attr { + name: "index_type" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "FloorDiv" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_UINT8 + type: DT_INT8 + type: DT_UINT16 + type: DT_INT16 + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "GreaterEqual" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type: DT_BOOL + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_INT64 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + } + op { + name: "Identity" + input_arg { + name: "input" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + } + op { + name: "L2Loss" + input_arg { + name: "t" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + } + op { + name: "Log1p" + input_arg { + name: "x" + type_attr: "T" + } + output_arg { + name: "y" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "MatMul" + input_arg { + name: "a" + type_attr: "T" + } + input_arg { + name: "b" + type_attr: "T" + } + output_arg { + name: "product" + type_attr: "T" + } + attr { + name: "transpose_a" + type: "bool" + default_value { + b: false + } + } + attr { + name: "transpose_b" + type: "bool" + default_value { + b: false + } + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "Maximum" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + } + } + } + is_commutative: true + } + op { + name: "Mean" + input_arg { + name: "input" + type_attr: "T" + } + input_arg { + name: "reduction_indices" + type_attr: "Tidx" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "keep_dims" + type: "bool" + default_value { + b: false + } + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tidx" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "MergeV2Checkpoints" + input_arg { + name: "checkpoint_prefixes" + type: DT_STRING + } + input_arg { + name: "destination_prefix" + type: DT_STRING + } + attr { + name: "delete_old_dirs" + type: "bool" + default_value { + b: true + } + } + is_stateful: true + } + op { + name: "Mul" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_UINT8 + type: DT_INT8 + type: DT_UINT16 + type: DT_INT16 + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + is_commutative: true + } + op { + name: "Neg" + input_arg { + name: "x" + type_attr: "T" + } + output_arg { + name: "y" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "NoOp" + } + op { + name: "Pack" + input_arg { + name: "values" + type_attr: "T" + number_attr: "N" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "N" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "T" + type: "type" + } + attr { + name: "axis" + type: "int" + default_value { + i: 0 + } + } + } + op { + name: "Placeholder" + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + } + attr { + name: "shape" + type: "shape" + default_value { + shape { + unknown_rank: true + } + } + } + } + op { + name: "PlaceholderWithDefault" + input_arg { + name: "input" + type_attr: "dtype" + } + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + } + attr { + name: "shape" + type: "shape" + } + } + op { + name: "Prod" + input_arg { + name: "input" + type_attr: "T" + } + input_arg { + name: "reduction_indices" + type_attr: "Tidx" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "keep_dims" + type: "bool" + default_value { + b: false + } + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tidx" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "RealDiv" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_UINT8 + type: DT_INT8 + type: DT_UINT16 + type: DT_INT16 + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "Reciprocal" + input_arg { + name: "x" + type_attr: "T" + } + output_arg { + name: "y" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "Reshape" + input_arg { + name: "tensor" + type_attr: "T" + } + input_arg { + name: "shape" + type_attr: "Tshape" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + attr { + name: "Tshape" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "RestoreV2" + input_arg { + name: "prefix" + type: DT_STRING + } + input_arg { + name: "tensor_names" + type: DT_STRING + } + input_arg { + name: "shape_and_slices" + type: DT_STRING + } + output_arg { + name: "tensors" + type_list_attr: "dtypes" + } + attr { + name: "dtypes" + type: "list(type)" + has_minimum: true + minimum: 1 + } + is_stateful: true + } + op { + name: "SaveV2" + input_arg { + name: "prefix" + type: DT_STRING + } + input_arg { + name: "tensor_names" + type: DT_STRING + } + input_arg { + name: "shape_and_slices" + type: DT_STRING + } + input_arg { + name: "tensors" + type_list_attr: "dtypes" + } + attr { + name: "dtypes" + type: "list(type)" + has_minimum: true + minimum: 1 + } + is_stateful: true + } + op { + name: "Select" + input_arg { + name: "condition" + type: DT_BOOL + } + input_arg { + name: "t" + type_attr: "T" + } + input_arg { + name: "e" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + } + op { + name: "Shape" + input_arg { + name: "input" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "out_type" + } + attr { + name: "T" + type: "type" + } + attr { + name: "out_type" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "ShardedFilename" + input_arg { + name: "basename" + type: DT_STRING + } + input_arg { + name: "shard" + type: DT_INT32 + } + input_arg { + name: "num_shards" + type: DT_INT32 + } + output_arg { + name: "filename" + type: DT_STRING + } + } + op { + name: "Sigmoid" + input_arg { + name: "x" + type_attr: "T" + } + output_arg { + name: "y" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "StringJoin" + input_arg { + name: "inputs" + type: DT_STRING + number_attr: "N" + } + output_arg { + name: "output" + type: DT_STRING + } + attr { + name: "N" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "separator" + type: "string" + default_value { + s: "" + } + } + } + op { + name: "Sub" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_UINT8 + type: DT_INT8 + type: DT_UINT16 + type: DT_INT16 + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "Sum" + input_arg { + name: "input" + type_attr: "T" + } + input_arg { + name: "reduction_indices" + type_attr: "Tidx" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "keep_dims" + type: "bool" + default_value { + b: false + } + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tidx" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "Tile" + input_arg { + name: "input" + type_attr: "T" + } + input_arg { + name: "multiples" + type_attr: "Tmultiples" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + attr { + name: "Tmultiples" + type: "type" + default_value { + type: DT_INT32 + } + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + } + op { + name: "TruncatedNormal" + input_arg { + name: "shape" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "seed" + type: "int" + default_value { + i: 0 + } + } + attr { + name: "seed2" + type: "int" + default_value { + i: 0 + } + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + } + } + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true + } + op { + name: "VariableV2" + output_arg { + name: "ref" + type_attr: "dtype" + is_ref: true + } + attr { + name: "shape" + type: "shape" + } + attr { + name: "dtype" + type: "type" + } + attr { + name: "container" + type: "string" + default_value { + s: "" + } + } + attr { + name: "shared_name" + type: "string" + default_value { + s: "" + } + } + is_stateful: true + } + op { + name: "ZerosLike" + input_arg { + name: "x" + type_attr: "T" + } + output_arg { + name: "y" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + } + } + tags: "serve" + tensorflow_version: "1.13.1" + tensorflow_git_version: "b\'unknown\'" + } + graph_def { + node { + name: "input" + op: "Placeholder" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + } + node { + name: "Placeholder" + op: "Placeholder" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + node { + name: "truncated_normal/shape" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 2 + } + } + tensor_content: "\000\006\000\000\016\000\000\000" + } + } + } + } + node { + name: "truncated_normal/mean" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 0.0 + } + } + } + } + node { + name: "truncated_normal/stddev" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 1.0 + } + } + } + } + node { + name: "truncated_normal/TruncatedNormal" + op: "TruncatedNormal" + input: "truncated_normal/shape" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "seed" + value { + i: 0 + } + } + attr { + key: "seed2" + value { + i: 0 + } + } + } + node { + name: "truncated_normal/mul" + op: "Mul" + input: "truncated_normal/TruncatedNormal" + input: "truncated_normal/stddev" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "truncated_normal" + op: "Add" + input: "truncated_normal/mul" + input: "truncated_normal/mean" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "Variable" + op: "VariableV2" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "container" + value { + s: "" + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + attr { + key: "shared_name" + value { + s: "" + } + } + } + node { + name: "Variable/Assign" + op: "Assign" + input: "Variable" + input: "truncated_normal" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "use_locking" + value { + b: true + } + } + attr { + key: "validate_shape" + value { + b: true + } + } + } + node { + name: "Variable/read" + op: "Identity" + input: "Variable" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "zeros" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { + size: 14 + } + } + float_val: 0.0 + } + } + } + } + node { + name: "Variable_1" + op: "VariableV2" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + attr { + key: "container" + value { + s: "" + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + dim { + size: 14 + } + } + } + } + attr { + key: "shared_name" + value { + s: "" + } + } + } + node { + name: "Variable_1/Assign" + op: "Assign" + input: "Variable_1" + input: "zeros" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + attr { + key: "use_locking" + value { + b: true + } + } + attr { + key: "validate_shape" + value { + b: true + } + } + } + node { + name: "Variable_1/read" + op: "Identity" + input: "Variable_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + } + node { + name: "MatMul" + op: "MatMul" + input: "input" + input: "Variable/read" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "transpose_a" + value { + b: false + } + } + attr { + key: "transpose_b" + value { + b: false + } + } + } + node { + name: "add" + op: "Add" + input: "MatMul" + input: "Variable_1/read" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logits" + op: "Identity" + input: "add" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/zeros_like" + op: "ZerosLike" + input: "logits" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/GreaterEqual" + op: "GreaterEqual" + input: "logits" + input: "logistic_loss/zeros_like" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/Select" + op: "Select" + input: "logistic_loss/GreaterEqual" + input: "logits" + input: "logistic_loss/zeros_like" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/Neg" + op: "Neg" + input: "logits" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/Select_1" + op: "Select" + input: "logistic_loss/GreaterEqual" + input: "logistic_loss/Neg" + input: "logits" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/mul" + op: "Mul" + input: "logits" + input: "Placeholder" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/sub" + op: "Sub" + input: "logistic_loss/Select" + input: "logistic_loss/mul" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/Exp" + op: "Exp" + input: "logistic_loss/Select_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss/Log1p" + op: "Log1p" + input: "logistic_loss/Exp" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "logistic_loss" + op: "Add" + input: "logistic_loss/sub" + input: "logistic_loss/Log1p" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "Const" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 2 + } + } + tensor_content: "\000\000\000\000\001\000\000\000" + } + } + } + } + node { + name: "Mean" + op: "Mean" + input: "logistic_loss" + input: "Const" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "L2Loss" + op: "L2Loss" + input: "Variable/read" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "mul/x" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 9.999999747378752e-05 + } + } + } + } + node { + name: "mul" + op: "Mul" + input: "mul/x" + input: "L2Loss" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "add_1" + op: "Add" + input: "Mean" + input: "mul" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "Const_1" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node { + name: "Mean_1" + op: "Mean" + input: "add_1" + input: "Const_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/Shape" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node { + name: "gradients/grad_ys_0" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 1.0 + } + } + } + } + node { + name: "gradients/Fill" + op: "Fill" + input: "gradients/Shape" + input: "gradients/grad_ys_0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "index_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/Mean_1_grad/Reshape/shape" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node { + name: "gradients/Mean_1_grad/Reshape" + op: "Reshape" + input: "gradients/Fill" + input: "gradients/Mean_1_grad/Reshape/shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/Mean_1_grad/Const" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node { + name: "gradients/Mean_1_grad/Tile" + op: "Tile" + input: "gradients/Mean_1_grad/Reshape" + input: "gradients/Mean_1_grad/Const" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tmultiples" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/Mean_1_grad/Const_1" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 1.0 + } + } + } + } + node { + name: "gradients/Mean_1_grad/truediv" + op: "RealDiv" + input: "gradients/Mean_1_grad/Tile" + input: "gradients/Mean_1_grad/Const_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/add_1_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/Mean_1_grad/truediv" + } + node { + name: "gradients/add_1_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/Mean_1_grad/truediv" + input: "^gradients/add_1_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/Mean_1_grad/truediv" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/add_1_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/Mean_1_grad/truediv" + input: "^gradients/add_1_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/Mean_1_grad/truediv" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/Mean_grad/Reshape/shape" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 2 + } + } + tensor_content: "\001\000\000\000\001\000\000\000" + } + } + } + } + node { + name: "gradients/Mean_grad/Reshape" + op: "Reshape" + input: "gradients/add_1_grad/tuple/control_dependency" + input: "gradients/Mean_grad/Reshape/shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1 + } + dim { + size: 1 + } + } + } + } + } + } + node { + name: "gradients/Mean_grad/Shape" + op: "Shape" + input: "logistic_loss" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/Mean_grad/Tile" + op: "Tile" + input: "gradients/Mean_grad/Reshape" + input: "gradients/Mean_grad/Shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tmultiples" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/Mean_grad/Shape_1" + op: "Shape" + input: "logistic_loss" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/Mean_grad/Shape_2" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node { + name: "gradients/Mean_grad/Const" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node { + name: "gradients/Mean_grad/Prod" + op: "Prod" + input: "gradients/Mean_grad/Shape_1" + input: "gradients/Mean_grad/Const" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/Mean_grad/Const_1" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node { + name: "gradients/Mean_grad/Prod_1" + op: "Prod" + input: "gradients/Mean_grad/Shape_2" + input: "gradients/Mean_grad/Const_1" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/Mean_grad/Maximum/y" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node { + name: "gradients/Mean_grad/Maximum" + op: "Maximum" + input: "gradients/Mean_grad/Prod_1" + input: "gradients/Mean_grad/Maximum/y" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/Mean_grad/floordiv" + op: "FloorDiv" + input: "gradients/Mean_grad/Prod" + input: "gradients/Mean_grad/Maximum" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/Mean_grad/Cast" + op: "Cast" + input: "gradients/Mean_grad/floordiv" + attr { + key: "DstT" + value { + type: DT_FLOAT + } + } + attr { + key: "SrcT" + value { + type: DT_INT32 + } + } + attr { + key: "Truncate" + value { + b: false + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/Mean_grad/truediv" + op: "RealDiv" + input: "gradients/Mean_grad/Tile" + input: "gradients/Mean_grad/Cast" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/mul_grad/Mul" + op: "Mul" + input: "gradients/add_1_grad/tuple/control_dependency_1" + input: "L2Loss" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/mul_grad/Mul_1" + op: "Mul" + input: "gradients/add_1_grad/tuple/control_dependency_1" + input: "mul/x" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/mul_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/mul_grad/Mul" + input: "^gradients/mul_grad/Mul_1" + } + node { + name: "gradients/mul_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/mul_grad/Mul" + input: "^gradients/mul_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/mul_grad/Mul" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/mul_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/mul_grad/Mul_1" + input: "^gradients/mul_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/mul_grad/Mul_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "gradients/logistic_loss_grad/Shape" + op: "Shape" + input: "logistic_loss/sub" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/logistic_loss_grad/Shape_1" + op: "Shape" + input: "logistic_loss/Log1p" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/logistic_loss_grad/BroadcastGradientArgs" + op: "BroadcastGradientArgs" + input: "gradients/logistic_loss_grad/Shape" + input: "gradients/logistic_loss_grad/Shape_1" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss_grad/Sum" + op: "Sum" + input: "gradients/Mean_grad/truediv" + input: "gradients/logistic_loss_grad/BroadcastGradientArgs" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/logistic_loss_grad/Reshape" + op: "Reshape" + input: "gradients/logistic_loss_grad/Sum" + input: "gradients/logistic_loss_grad/Shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss_grad/Sum_1" + op: "Sum" + input: "gradients/Mean_grad/truediv" + input: "gradients/logistic_loss_grad/BroadcastGradientArgs:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/logistic_loss_grad/Reshape_1" + op: "Reshape" + input: "gradients/logistic_loss_grad/Sum_1" + input: "gradients/logistic_loss_grad/Shape_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/logistic_loss_grad/Reshape" + input: "^gradients/logistic_loss_grad/Reshape_1" + } + node { + name: "gradients/logistic_loss_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/logistic_loss_grad/Reshape" + input: "^gradients/logistic_loss_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss_grad/Reshape" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/logistic_loss_grad/Reshape_1" + input: "^gradients/logistic_loss_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss_grad/Reshape_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/L2Loss_grad/mul" + op: "Mul" + input: "Variable/read" + input: "gradients/mul_grad/tuple/control_dependency_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Shape" + op: "Shape" + input: "logistic_loss/Select" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Shape_1" + op: "Shape" + input: "logistic_loss/mul" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/BroadcastGradientArgs" + op: "BroadcastGradientArgs" + input: "gradients/logistic_loss/sub_grad/Shape" + input: "gradients/logistic_loss/sub_grad/Shape_1" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Sum" + op: "Sum" + input: "gradients/logistic_loss_grad/tuple/control_dependency" + input: "gradients/logistic_loss/sub_grad/BroadcastGradientArgs" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Reshape" + op: "Reshape" + input: "gradients/logistic_loss/sub_grad/Sum" + input: "gradients/logistic_loss/sub_grad/Shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Sum_1" + op: "Sum" + input: "gradients/logistic_loss_grad/tuple/control_dependency" + input: "gradients/logistic_loss/sub_grad/BroadcastGradientArgs:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Neg" + op: "Neg" + input: "gradients/logistic_loss/sub_grad/Sum_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/Reshape_1" + op: "Reshape" + input: "gradients/logistic_loss/sub_grad/Neg" + input: "gradients/logistic_loss/sub_grad/Shape_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/logistic_loss/sub_grad/Reshape" + input: "^gradients/logistic_loss/sub_grad/Reshape_1" + } + node { + name: "gradients/logistic_loss/sub_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/logistic_loss/sub_grad/Reshape" + input: "^gradients/logistic_loss/sub_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/sub_grad/Reshape" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/sub_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/logistic_loss/sub_grad/Reshape_1" + input: "^gradients/logistic_loss/sub_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/sub_grad/Reshape_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Log1p_grad/add/x" + op: "Const" + input: "^gradients/logistic_loss_grad/tuple/control_dependency_1" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 1.0 + } + } + } + } + node { + name: "gradients/logistic_loss/Log1p_grad/add" + op: "Add" + input: "gradients/logistic_loss/Log1p_grad/add/x" + input: "logistic_loss/Exp" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Log1p_grad/Reciprocal" + op: "Reciprocal" + input: "gradients/logistic_loss/Log1p_grad/add" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Log1p_grad/mul" + op: "Mul" + input: "gradients/logistic_loss_grad/tuple/control_dependency_1" + input: "gradients/logistic_loss/Log1p_grad/Reciprocal" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_grad/zeros_like" + op: "ZerosLike" + input: "logits" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_grad/Select" + op: "Select" + input: "logistic_loss/GreaterEqual" + input: "gradients/logistic_loss/sub_grad/tuple/control_dependency" + input: "gradients/logistic_loss/Select_grad/zeros_like" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_grad/Select_1" + op: "Select" + input: "logistic_loss/GreaterEqual" + input: "gradients/logistic_loss/Select_grad/zeros_like" + input: "gradients/logistic_loss/sub_grad/tuple/control_dependency" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/logistic_loss/Select_grad/Select" + input: "^gradients/logistic_loss/Select_grad/Select_1" + } + node { + name: "gradients/logistic_loss/Select_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/logistic_loss/Select_grad/Select" + input: "^gradients/logistic_loss/Select_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/Select_grad/Select" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/logistic_loss/Select_grad/Select_1" + input: "^gradients/logistic_loss/Select_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/Select_grad/Select_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Shape" + op: "Shape" + input: "logits" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Shape_1" + op: "Shape" + input: "Placeholder" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/BroadcastGradientArgs" + op: "BroadcastGradientArgs" + input: "gradients/logistic_loss/mul_grad/Shape" + input: "gradients/logistic_loss/mul_grad/Shape_1" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Mul" + op: "Mul" + input: "gradients/logistic_loss/sub_grad/tuple/control_dependency_1" + input: "Placeholder" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Sum" + op: "Sum" + input: "gradients/logistic_loss/mul_grad/Mul" + input: "gradients/logistic_loss/mul_grad/BroadcastGradientArgs" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Reshape" + op: "Reshape" + input: "gradients/logistic_loss/mul_grad/Sum" + input: "gradients/logistic_loss/mul_grad/Shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Mul_1" + op: "Mul" + input: "logits" + input: "gradients/logistic_loss/sub_grad/tuple/control_dependency_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Sum_1" + op: "Sum" + input: "gradients/logistic_loss/mul_grad/Mul_1" + input: "gradients/logistic_loss/mul_grad/BroadcastGradientArgs:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/Reshape_1" + op: "Reshape" + input: "gradients/logistic_loss/mul_grad/Sum_1" + input: "gradients/logistic_loss/mul_grad/Shape_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/logistic_loss/mul_grad/Reshape" + input: "^gradients/logistic_loss/mul_grad/Reshape_1" + } + node { + name: "gradients/logistic_loss/mul_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/logistic_loss/mul_grad/Reshape" + input: "^gradients/logistic_loss/mul_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/mul_grad/Reshape" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/mul_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/logistic_loss/mul_grad/Reshape_1" + input: "^gradients/logistic_loss/mul_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/mul_grad/Reshape_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Exp_grad/mul" + op: "Mul" + input: "gradients/logistic_loss/Log1p_grad/mul" + input: "logistic_loss/Exp" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_1_grad/zeros_like" + op: "ZerosLike" + input: "logistic_loss/Neg" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_1_grad/Select" + op: "Select" + input: "logistic_loss/GreaterEqual" + input: "gradients/logistic_loss/Exp_grad/mul" + input: "gradients/logistic_loss/Select_1_grad/zeros_like" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_1_grad/Select_1" + op: "Select" + input: "logistic_loss/GreaterEqual" + input: "gradients/logistic_loss/Select_1_grad/zeros_like" + input: "gradients/logistic_loss/Exp_grad/mul" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_1_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/logistic_loss/Select_1_grad/Select" + input: "^gradients/logistic_loss/Select_1_grad/Select_1" + } + node { + name: "gradients/logistic_loss/Select_1_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/logistic_loss/Select_1_grad/Select" + input: "^gradients/logistic_loss/Select_1_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/Select_1_grad/Select" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Select_1_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/logistic_loss/Select_1_grad/Select_1" + input: "^gradients/logistic_loss/Select_1_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/Select_1_grad/Select_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/logistic_loss/Neg_grad/Neg" + op: "Neg" + input: "gradients/logistic_loss/Select_1_grad/tuple/control_dependency" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/AddN" + op: "AddN" + input: "gradients/logistic_loss/Select_grad/tuple/control_dependency" + input: "gradients/logistic_loss/mul_grad/tuple/control_dependency" + input: "gradients/logistic_loss/Select_1_grad/tuple/control_dependency_1" + input: "gradients/logistic_loss/Neg_grad/Neg" + attr { + key: "N" + value { + i: 4 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/logistic_loss/Select_grad/Select" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/add_grad/Shape" + op: "Shape" + input: "MatMul" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node { + name: "gradients/add_grad/Shape_1" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 14 + } + } + } + } + node { + name: "gradients/add_grad/BroadcastGradientArgs" + op: "BroadcastGradientArgs" + input: "gradients/add_grad/Shape" + input: "gradients/add_grad/Shape_1" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + } + node { + name: "gradients/add_grad/Sum" + op: "Sum" + input: "gradients/AddN" + input: "gradients/add_grad/BroadcastGradientArgs" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/add_grad/Reshape" + op: "Reshape" + input: "gradients/add_grad/Sum" + input: "gradients/add_grad/Shape" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/add_grad/Sum_1" + op: "Sum" + input: "gradients/AddN" + input: "gradients/add_grad/BroadcastGradientArgs:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + } + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node { + name: "gradients/add_grad/Reshape_1" + op: "Reshape" + input: "gradients/add_grad/Sum_1" + input: "gradients/add_grad/Shape_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/add_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/add_grad/Reshape" + input: "^gradients/add_grad/Reshape_1" + } + node { + name: "gradients/add_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/add_grad/Reshape" + input: "^gradients/add_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/add_grad/Reshape" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/add_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/add_grad/Reshape_1" + input: "^gradients/add_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/add_grad/Reshape_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/MatMul_grad/MatMul" + op: "MatMul" + input: "gradients/add_grad/tuple/control_dependency" + input: "Variable/read" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + } + attr { + key: "transpose_a" + value { + b: false + } + } + attr { + key: "transpose_b" + value { + b: true + } + } + } + node { + name: "gradients/MatMul_grad/MatMul_1" + op: "MatMul" + input: "input" + input: "gradients/add_grad/tuple/control_dependency" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "transpose_a" + value { + b: true + } + } + attr { + key: "transpose_b" + value { + b: false + } + } + } + node { + name: "gradients/MatMul_grad/tuple/group_deps" + op: "NoOp" + input: "^gradients/MatMul_grad/MatMul" + input: "^gradients/MatMul_grad/MatMul_1" + } + node { + name: "gradients/MatMul_grad/tuple/control_dependency" + op: "Identity" + input: "gradients/MatMul_grad/MatMul" + input: "^gradients/MatMul_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/MatMul_grad/MatMul" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + } + } + node { + name: "gradients/MatMul_grad/tuple/control_dependency_1" + op: "Identity" + input: "gradients/MatMul_grad/MatMul_1" + input: "^gradients/MatMul_grad/tuple/group_deps" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/MatMul_grad/MatMul_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "gradients/AddN_1" + op: "AddN" + input: "gradients/L2Loss_grad/mul" + input: "gradients/MatMul_grad/tuple/control_dependency_1" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@gradients/L2Loss_grad/mul" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "GradientDescent/learning_rate" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 0.5 + } + } + } + } + node { + name: "GradientDescent/update_Variable/ApplyGradientDescent" + op: "ApplyGradientDescent" + input: "Variable" + input: "GradientDescent/learning_rate" + input: "gradients/AddN_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "use_locking" + value { + b: false + } + } + } + node { + name: "GradientDescent/update_Variable_1/ApplyGradientDescent" + op: "ApplyGradientDescent" + input: "Variable_1" + input: "GradientDescent/learning_rate" + input: "gradients/add_grad/tuple/control_dependency_1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + attr { + key: "use_locking" + value { + b: false + } + } + } + node { + name: "GradientDescent" + op: "NoOp" + input: "^GradientDescent/update_Variable/ApplyGradientDescent" + input: "^GradientDescent/update_Variable_1/ApplyGradientDescent" + } + node { + name: "Sigmoid" + op: "Sigmoid" + input: "logits" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "init" + op: "NoOp" + input: "^Variable/Assign" + input: "^Variable_1/Assign" + } + node { + name: "save/filename/input" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "model" + } + } + } + } + node { + name: "save/filename" + op: "PlaceholderWithDefault" + input: "save/filename/input" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "shape" + value { + shape { + } + } + } + } + node { + name: "save/Const" + op: "PlaceholderWithDefault" + input: "save/filename" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "shape" + value { + shape { + } + } + } + } + node { + name: "save/StringJoin/inputs_1" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "_temp_171a1c01c56c46198d395a032e5b295b/part" + } + } + } + } + node { + name: "save/StringJoin" + op: "StringJoin" + input: "save/Const" + input: "save/StringJoin/inputs_1" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "separator" + value { + s: "" + } + } + } + node { + name: "save/num_shards" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node { + name: "save/ShardedFilename/shard" + op: "Const" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 0 + } + } + } + } + node { + name: "save/ShardedFilename" + op: "ShardedFilename" + input: "save/StringJoin" + input: "save/ShardedFilename/shard" + input: "save/num_shards" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "save/SaveV2/tensor_names" + op: "Const" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + dim { + size: 2 + } + } + string_val: "Variable" + string_val: "Variable_1" + } + } + } + } + node { + name: "save/SaveV2/shape_and_slices" + op: "Const" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + dim { + size: 2 + } + } + string_val: "" + string_val: "" + } + } + } + } + node { + name: "save/SaveV2" + op: "SaveV2" + input: "save/ShardedFilename" + input: "save/SaveV2/tensor_names" + input: "save/SaveV2/shape_and_slices" + input: "Variable" + input: "Variable_1" + device: "/device:CPU:0" + attr { + key: "dtypes" + value { + list { + type: DT_FLOAT + type: DT_FLOAT + } + } + } + } + node { + name: "save/control_dependency" + op: "Identity" + input: "save/ShardedFilename" + input: "^save/SaveV2" + device: "/device:CPU:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@save/ShardedFilename" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "save/MergeV2Checkpoints/checkpoint_prefixes" + op: "Pack" + input: "save/ShardedFilename" + input: "^save/control_dependency" + device: "/device:CPU:0" + attr { + key: "N" + value { + i: 1 + } + } + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1 + } + } + } + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node { + name: "save/MergeV2Checkpoints" + op: "MergeV2Checkpoints" + input: "save/MergeV2Checkpoints/checkpoint_prefixes" + input: "save/Const" + device: "/device:CPU:0" + attr { + key: "delete_old_dirs" + value { + b: true + } + } + } + node { + name: "save/Identity" + op: "Identity" + input: "save/Const" + input: "^save/MergeV2Checkpoints" + input: "^save/control_dependency" + device: "/device:CPU:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + } + } + } + } + } + node { + name: "save/RestoreV2/tensor_names" + op: "Const" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + dim { + size: 2 + } + } + string_val: "Variable" + string_val: "Variable_1" + } + } + } + } + node { + name: "save/RestoreV2/shape_and_slices" + op: "Const" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 2 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + dim { + size: 2 + } + } + string_val: "" + string_val: "" + } + } + } + } + node { + name: "save/RestoreV2" + op: "RestoreV2" + input: "save/Const" + input: "save/RestoreV2/tensor_names" + input: "save/RestoreV2/shape_and_slices" + device: "/device:CPU:0" + attr { + key: "_output_shapes" + value { + list { + shape { + unknown_rank: true + } + shape { + unknown_rank: true + } + } + } + } + attr { + key: "dtypes" + value { + list { + type: DT_FLOAT + type: DT_FLOAT + } + } + } + } + node { + name: "save/Assign" + op: "Assign" + input: "Variable" + input: "save/RestoreV2" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "use_locking" + value { + b: true + } + } + attr { + key: "validate_shape" + value { + b: true + } + } + } + node { + name: "save/Assign_1" + op: "Assign" + input: "Variable_1" + input: "save/RestoreV2:1" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Variable_1" + } + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + attr { + key: "use_locking" + value { + b: true + } + } + attr { + key: "validate_shape" + value { + b: true + } + } + } + node { + name: "save/restore_shard" + op: "NoOp" + input: "^save/Assign" + input: "^save/Assign_1" + } + node { + name: "save/restore_all" + op: "NoOp" + input: "^save/restore_shard" + } + versions { + producer: 27 + } + } + saver_def { + filename_tensor_name: "save/Const:0" + save_tensor_name: "save/Identity:0" + restore_op_name: "save/restore_all" + max_to_keep: 5 + sharded: true + keep_checkpoint_every_n_hours: 10000.0 + version: V2 + } + collection_def { + key: "train_op" + value { + node_list { + value: "GradientDescent" + } + } + } + collection_def { + key: "trainable_variables" + value { + bytes_list { + value: "\n\nVariable:0\022\017Variable/Assign\032\017Variable/read:02\022truncated_normal:08\001" + value: "\n\014Variable_1:0\022\021Variable_1/Assign\032\021Variable_1/read:02\007zeros:08\001" + } + } + } + collection_def { + key: "variables" + value { + bytes_list { + value: "\n\nVariable:0\022\017Variable/Assign\032\017Variable/read:02\022truncated_normal:08\001" + value: "\n\014Variable_1:0\022\021Variable_1/Assign\032\021Variable_1/read:02\007zeros:08\001" + } + } + } + signature_def { + key: "serving_default" + value { + inputs { + key: "input" + value { + name: "input:0" + dtype: DT_FLOAT + tensor_shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + outputs { + key: "output" + value { + name: "Sigmoid:0" + dtype: DT_FLOAT + tensor_shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + method_name: "tensorflow/serving/predict" + } + } +} diff --git a/model-integration/src/test/models/tensorflow/regression/test1/variables/variables.data-00000-of-00001 b/model-integration/src/test/models/tensorflow/regression/test1/variables/variables.data-00000-of-00001 Binary files differnew file mode 100644 index 00000000000..083abe4a917 --- /dev/null +++ b/model-integration/src/test/models/tensorflow/regression/test1/variables/variables.data-00000-of-00001 diff --git a/model-integration/src/test/models/tensorflow/regression/test1/variables/variables.index b/model-integration/src/test/models/tensorflow/regression/test1/variables/variables.index Binary files differnew file mode 100644 index 00000000000..43ceede272c --- /dev/null +++ b/model-integration/src/test/models/tensorflow/regression/test1/variables/variables.index diff --git a/model-integration/src/test/models/tensorflow/regression/test2/saved_model.pbtxt b/model-integration/src/test/models/tensorflow/regression/test2/saved_model.pbtxt new file mode 100644 index 00000000000..112f9a28cb7 --- /dev/null +++ b/model-integration/src/test/models/tensorflow/regression/test2/saved_model.pbtxt @@ -0,0 +1,389 @@ +saved_model_schema_version: 1 +meta_graphs { + meta_info_def { + stripped_op_list { + op { + name: "Add" + input_arg { + name: "x" + type_attr: "T" + } + input_arg { + name: "y" + type_attr: "T" + } + output_arg { + name: "z" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_UINT8 + type: DT_INT8 + type: DT_INT16 + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + type: DT_STRING + } + } + } + } + op { + name: "Const" + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "value" + type: "tensor" + } + attr { + name: "dtype" + type: "type" + } + } + op { + name: "Identity" + input_arg { + name: "input" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + } + } + op { + name: "MatMul" + input_arg { + name: "a" + type_attr: "T" + } + input_arg { + name: "b" + type_attr: "T" + } + output_arg { + name: "product" + type_attr: "T" + } + attr { + name: "transpose_a" + type: "bool" + default_value { + b: false + } + } + attr { + name: "transpose_b" + type: "bool" + default_value { + b: false + } + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_BFLOAT16 + type: DT_HALF + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } + } + op { + name: "Placeholder" + output_arg { + name: "output" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + } + attr { + name: "shape" + type: "shape" + default_value { + shape { + unknown_rank: true + } + } + } + } + } + tags: "serve" + tensorflow_version: "1.13.1" + tensorflow_git_version: "b\'unknown\'" + } + graph_def { + node { + name: "input" + op: "Placeholder" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_DOUBLE + } + } + attr { + key: "shape" + value { + shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + } + node { + name: "Const" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_DOUBLE + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_DOUBLE + tensor_shape { + dim { + size: 1536 + } + dim { + size: 14 + } + } + tensor_content: "\306C\326o\340\326\223?\020\277\341\231\265\306\270?\316P\000\311KI\207\277\220\227\253 \371\377\301\277v\017\032|E<\276\277\341Z\0000\306X\235\277\022\234<\027\361~p?D\223\252\246^~\305?;\245\032\230\276\206\301\277\332\273f\313o\255\275\277\225\371\337T\205q\257?\344l\364\354\365iM?\333p\307\334E\010\304\277Aj6X\256?\275\277\236\217V\305\021|\300??\022\307j\253\030\301?n)\022\372Kf\246?\023\010\201\026\212\243\204\277\244\017\025\200I*\243\277\332k\317\331\036\003\267?\300\334pk\254A\301\277\235y\006\310jR\300?\001 \331\267\276\315\220?\350Y\221w\262K\200\277\024\256_\t\361\311\301?\326H\352h\226\203\260?\0237\325z\351\353\266\277.W\003\312\016g\220\277\327_\265\225\226\353\243\277N\006\376V\262\260\304?^Z\265\276\206\211\253\277\234/\315\345qM\300\277!\r\332-\263\364\221?0x\361O\221\020\261\277\343S\030|\376&\211?\272v\000{\002S\317\277\243\026:\022_\247\273?\3632&\364\360\210\224?\r\027\212\022x\224\246?((UB\235\331\223\277y\374\253\333\277\226\221?\003\004\363(\2270K?\303\370\363\270\302u\263\277&L^.\260\201\261\277\275,\250\263\003\261\251\277i\271I\261LX\261?*\347\232\245\205F\307?,\273}\203\033\251\214\277\2743\371l\037\324\272?\240\022\305\372l\252\247?\366 \300}\247a\312?\2277\227\332\340\004\247?E\327\353\221\215`\274\277\036\235\215K\247C\261\277\361\233\302H\\W\226?\375\270\022\021n\211\316\277\300\273\314!\021;\300\277a\231\255\356\312\375\310?e\306\323\212\213\375\207?\243@\212S\307\226\262\277\201\007r\200x\266\243\2776\355\264\200\224\332\226\277HVA\002\213\372\312?\203EZ3\367\337\266?\036\322\374\370\320\324\306\277D\231?\257b^\177?c\363\365z\363ZX\277\373\242TTS#\303\277\037/r\217\224\242\271\277\301\017\356\343\022\363\263\277\315%\261\213\317\301|\277\005\357\013\260M\010\320?\257^A\360\257V\262?\350f\367!\366?\275?\004\237\311\022\023F\303\277r\305^\300\243\361\300?\217\017*Q\276\201\267?\025x\212\225\343\265\304\277ey\271\324\353\033|?\246wO\244$)\240?\204\335\343\264L\242\257\277\315>y\350\346\314\305?\277\256\335%57\254?\235\024\335+FA\237\277*\244\0329\030\200l\2779\251,w\035\006\302\277\274\010\271\303\233:\236?\'9 3\346-\177?\275\320\177\351xr\302?\037<mds\021\272?ua\353~\371\275\276\277\346@\326\353T\335\227?\235*\031\227\244\006\216\277\353\177k\221\374\361}?\n\233\372Y\255L\301?e\276\364/\354\336\260\277a+U\223\216\202\245?\214\3049\306\377\361\204\277.w\252Q\223\036]\277)G\363%/\212\241?\375\252\342\027E\361\266\277]\200\252\034\302\346p?\3516\266\030\177w\312?|\014\363%\3342\226\277`6So\034\020\245?\212\'\305\377Q\344\272\277K\242]\362\014\335\253?\256\215\274\016\200\307\220?H4\370:\276\332\270?PC@dEW\206\277\237\202n\202m.\240\277\344\204w\3204]\300\277\256\353ZQ\325P\271?\311\210\262p\3524\264\2775#\025\031\262\256\240\277ku\311<\006\315\240\277\311\346\267\260\007/\252\277\t\215B\001\276.\301?\255\020\311\276\026Ay\277\372\t7b2\355\217\277\232\344\303\240F\245\307\277Gs\366_\263\220\253?\333\nA\" lv\277Q\264|\022f\232\245?\037Q\306z\232\324\271\2777\225\343p\230]\244?\371t\226F\035Zb?\267e6\331\306\032\303?\340\205\254\324\313\215\256\277\014U\r\366A\212\317\277\223\'.l\372K\270\277\226\215\020\236\233\017A?n|,W\234B\241?\330\253\022VW&j?\233\200k\303[\323\245\277L{3}J\033\237\277\353\333\214_\010R\225?\025Q\273\364\261S\211?\205b\006\200b\020\260\277tl\350]\342\007\300?Z\244f*\341\025\203?\334+\220#\226\377\271?\305\036\332,\362J\234\277\3632\232dZ[\227\277\0202E\313\230\322\274\277\205\036\235E\306\350\217?!B\354\364\325\301\303?L1\004\252\275\215\237\277\030?5\341\254\373\301\277\'\031\256\240\026\223~\277\217\002\2600\216\232\224?{\336\270\215_\371\272\277?\267\330V\1775\306\277f\200\021F\321\206\225?\303\341\377e/h\232\277\003\\>G\377\\\252?!\307\205\342,\222\255\277!m6\000~\266\223?<(\207\362\004\352\311\277\245\307\010>\257\343\264?\022\361\262\362\261Xs?<\200\362\"\013\223\300?z\2050\350C\260\304?0y\265\r\273\260\225?00\221ICa\271\277Q\3722RL.\252\277\371\036y\034\374\346\272\277\344\320\273\002\374a\225?[\022!>r\250\240\277\213\232&*x\254\237\277\332:\023\321\250W\270?\262S\367\252y\321\266?wQ/\256#\222\247\277\337+\257\226\355R\266?\217\216YI\317\203\241\277G%\322f>c\264\277M]\377\330O\026\300\2771\332\222\345-\304\265?\370\343\333a\346\225\304?o\023\303/O\330\276?\016c\265\225W\211\312?\303\250\230\361p\337|\277\306\315\360\310\372\365\267?\357\252\315\"\016\'\306?\272#[\364\016\251\271\277\013\241\007\023\317\372\273?8! \022(\362\304?n\210\2722\375L\230\277\346#\" \350\004\264\277\203\334Q\343\2455\311\277[\317\t\316\222)\314?p}\206\205\250\242\253\277\362\230\320\224d\347\305\277\205\325\211:zq\274?\322\220\367y\223\356\262\277g\201\021\233\356H\203\277\210{n\310:\034\241\277\356:\024}v\026\262\277\273\221|(\241\232\223\277*z\236m\276\377\203?\033\375a\312\334o\260?FXb\030j\021\306\277\027\260\367>\016\002\320?\246\327\215\255\301\256\244?\002\3201\264}\324\315\277\240\241\326\362\177-\300\277\2610\224#\305i\240\277\350ou\362\304\246\265\277\257l7\'u\326\300\277\234\344\232\233!\230\247\277\001/qDg\317\240?\212D\252\024\375&\272?\220\320_.|\333\262\277\346(pe\'\251\300\277E\300\255X\347s\243\277\377\034\345E\247\237\261?[\204\030\211\216\301\303?\n08\357@\222\250\277\217\267d\213\303x\311\277\225\017\320\352\212\r\227?N1M\233I\244\314?I\242\340N\";\262\277\356\327\034\317o\332\273?\026\016n\342X%\267\277h\211\002\262\336\236\223\277\245!n\033x\222\314?\013\006\353\370\274\025\255\277\000\215\237\300W`\226?V\204\252\256\034]}\277\240\353\232~\314\327&?e\010\205\222\rB\301\277paq\376c\003\305?5aA\254\225\031t\277\242\230\327z\341\017\220\277@e\367\332\302\314\265\277\362\376\313\351\327q\241\277FU\376*\361\"\300\277@\271,\363~\331\244\277\242l\357\202bF\307?x\r\377\301F\377\254?@;;\250&K\263?\235|\243\022J\035q?\'\240\005\020\272!\253\2776\334@\032h\357\245?N_\02423\343\302\277\222u\370ug\240\270?\007\222\224+\216\246\223?\332\253Ns\274b\212\277\261\211\320\310\003\343\202\277]\251\240\016\261\323\276?\236\215\212\n\270\271\300?Q\304\223\246\036\363\300?Wo\331;\227\200\257?\247\375\257\203\002X\255\277\330\310!\006\376B\222?hb\262\231\377\356\272?W\204rt\0378\320\277\201\256>V\336\231\213\277mHi\241V\236\246\277\250>@u\314\266\260\277s\317\251H;\257\270?\335\256i\004y\374\266?\001\262>\373\326\311t\277\301p\002\2409O\313\277\315\300W\253\265\310\264?K\347\234\\\321\006\263?\350\304l(c\021\264\277\031\334R\333\010l\307?\244\364\030\330N\217\225\277\262\312s\267\316\023\271\277\024G%\257\224\306\213?\255\232\215^\374\371\244\277bu!\301\354\361\321\277t\243\2336D\345\260?\303\212\026c\312\330\266??\216\2433)]\237?k\316\014n`J\306?rOS\241\373@\262\277\217\264{n\323\345\323?\354Q\337vA\263\260?\253\374\264\325A\000\254\277\003\002\256\266\304\\\304\277}\232&\'\302\300\270\277\256t\332z\344\362\277?:\210vy\025\223\246?\327\3678>\370!\302\277\202\245\210\036\372\304\271\277\\i\346~\355\327\207\277\"v9\361Nr\263?\205\323c\026\'6\265?E\nG5\034`\257?aX\266JLe\307?\324\217.\223\323f\272\277\234\366\301^\007\001\224?64d\205\3402\220?^\376\022=\233\224\315\277.<}\034e\321\246\277m\323\354\000\335\002\272\277t7\016\265m\346\240?\nFB\307\2644\257\277\013\2667W\023\210i\2770a\310\231A\205\224?\336V\364\372\244x\244\277\266\347\023!I\230\243?#B\3774<\344\250\277\261\347\300Y\371\033\251?\3005/T\370\250\255\277fe\031&lQ\267?\201\033(?\330\335\242?\206\220\372\264\324\257\274?\036X\017Qw\245~?H\301\177\014\320/\305\277\000Y\361\233P\255\263?_\240\022\341Q\000\217\277\262|\206\222\234\025\267?K\256I\276\230\n\310\277S\3749\351\252\220\227?\033 \377\224\227\247\211\277 \214\036\344u\201\307\277H\027\345x\262\300\244\277\220\026\224#n\364\303?\\Q\323\233\375\224\245?\037]!@\376\273w?\024D\2725s(\255?\223\364\307\305\025_\253\277a\352\275`\266z\227?\344,\304\371\357E\305?\373\266cR/N\272\277m\255\213q8sK?r\216\350H\307\242\311?\215S`\'\000\205\227\277\027\210\326\016\236\262\253\277sM<\260rtt?%\220Mt}Q\264?)\252\340\034\2716\247?p\272\"%\334\367\250\2775S\003o\313\240\250?w\324B\360\2212\301\277\374\215\221\332H\222s\277#\264W\r\317\305u\277\322\255\257\314\213\352\265\277Og\201ev\330\227\277\262\306\230\226u~\272?\337\273\177@\034\314\257?F\215U\353\357\037\272\277\323\307V\360T<\266?\0350\304\255o\002\321\277\334\352\301\334P\317\265?\353\333\t\271I\325\264\277\026\016\370\242\243\337n?\350%\033\302\327\310\231?\177\272,<W6\252\277\375}\267\234\246\373\202?b2\265u\375I\235?\356\335\251\010\275\332\322\277$1\332\311gn\301?\257m\327p\220{\264\277\020\251;&ZK\300?\247\177,\340S\036\271?>\224\233\247\222J\244?F\345 \260\243\252\276?\306\010\3153\211\024\261\277\2671\254S\376$\321?\006\244\230o\250\300\300\277D9\305\335l|\276?H{6\n\361\227\261\277\021K\001\225u\323\223\277\247\000S\237I\313\264\277\225m\277\016\264M\225\277\366\262Z\177\244N-\277\356\275 \342\310\304\222\277m\221<\312P\263\264\277S\250\005K\3408\311\277M\350#\014\016c\211\277\t\317}_M9y\277\352\261|a}\022\227\277\243T0\021\026\322\262\277&z\213>\205\365\267?T\273\032\344~9\300?vM\276\207\276Q\255\277\241\250\347\371H\343\244\277\2420\230\233nf\266?+\365(\214,g\311\277\344\303#\304\202\010\263?/TK\311\227]\226?\215u\337\311\330\025\311\277Zp\374\344E^\266?\3075\230\260\302>\245?\342\024s\001U\224\271?W\303\225p3O\251?\0311\033\312+\357\270\277\202\010\337\333\0106\340>\317\321r\312\354\310\273\277\207\336\226=\337O\276\277r\234\266t\253#\247\277\240q\007\342\275K\310\277+\311\247RR\366\265?\t\335\236\216+\034\302?6r\217_\003\237\300?\207\216$#\1775\245\277,&k\202\241\265\316?\244\274$\241\367^\246\277\204\327\305fHj\306?\304\256\223\371t\226\303?\023d\301\351\375^\264\277\256i#\020S\243\237?\205\035\2226\361B\224?6]\237\330\241\220\267\277\265\311O\334d:\251\277\306\366\\\037/\332\266?z\t\036\315b\371\234\277F\317A?\254\322\264\277\254g\310q?\212\276?\332~\025\000\\\345\230\277\236\347\221`|\322\277\277a\320t\211\030\311\233\2777|X\336\356\306\243\277h*\200r\321$\242?\3343{\277\277\205\311?\313\016\345\0361q\300?O\226\245\310\245\343\271?[\213\332p\3248X\277\027\006\361\307Z\225\265?\004\007TA\373!\223\277\3263\\\241G\020\236\277\3578\372\310\303\010\246\277\350\016\345`\022\035\222\277\321S7\3055Z\257\277\373F\326sjg\242?\177\314\220\212\317G\247?\204\t\256\037wo\225\277osg\363wC\267\277\376\250{\246@\330\222?\324\014}\374\r\225\242\277%\331!n3\334\246?\240p\316\005\306U|\277s\027J(\307\370\256?=\274\300=\014\024\222?; l\324\306\264\264\277q\200\035*\216]\243?\027\016G\301\375\\\224?\226\033\270QWm\302?Dt\331\302u1\303?\255\311\025\215\244\300\216?o\211\264\350\272\345S?X`\231:\224YS\277$\226\336}\027 \225\277l\203\265\375\367\306\314\277:2\307\365\014\'\246\277DU\353\224\371[y?\337\3533\232\223\274\251?:\272d\256\364\326\260\277B\326\211S\255\022\207\277=\313\3234<\247\276?+\236\310\263S\'\246?T\030|\305\206\023\304?3T\223H\001\300\325\277\322\216\350\312L\356\233?\362r\247\201\231\251\243\277b\361\177\235\262\244\260?77\326\266\222\027\322?\275\322\356\3358\312\234\277\\J\017\2700\271\264?\036\355\t\0143\010\247\277\264\340\331\300u\230\307?\315\2665)\036x\275\277&\377\275\243\304{\274?4\314B\342\026\010U?5\232j\006\240\230\245\277\270d\367\007\272\020\274?\227\221xzR\316\275\277\027)\361\3176\274\240?X\205\336\261F?\217?\273I\226\227\355\r\301\277y\361tPpz\200?\267f\326\242>\244\277\277\211\214Yinu\270\277\227\324\241\210\362\350\301?\317H\356b\371\347\250\277\327\376\232\325W\345\177?\233U\240\305\025\306\262?;\327\222\353\272 \202\277\200\234\020\226v\271\300?\245,\010\227\267\017\313\277[\320\010m\r\341\204?\214\216\256@\322\026\224\277\255\326t2\001\225\216\277K\223^\234\357j\221?:\354\267\3534\235\243??\001c\245B\331\252\277W\325\350n\034H\274\277\035\224`\003\260+\312\277\372\356S\265\246\377\207?\305\230\231\256\333\203\272\277\316k\035\202\261\037\303?\245\023\241\243\025\315\247?\276\216H{\272\223\301\277gb\355\227\205\034\314?\026N\t\274\303/\300\277\302\275]\353W\301\223?\026\254h\026C4\273\277\261\305\342D\372v\260\277~-\347\301\274h\320?\nj\255\261\027\027\213?\021Vn\025y\201\266\277\326\210e\325\266\373\271\277\254\001d\2455\326\243\277}ut\221G\262\303\277Z^\334Z(}\274?\262\345\177\231\276\357\260\277\0214\324\252\314Y\271?\360\007\337>LL\320\277E\210\016\037u\177\314?+\200\250\342\343i\261\277\261\201\263\3617b\301?\351<\212E\234E\304?\027\325\3337\n\325\312?\014\2363f$\237\250\277\247\327ziu\341\264\277\231N\010$|\263\232\277\355\315\271\200uq\233\277\201R=\004s\200\250\277\001V\334\346\240I\246?\306\021\314\376\034T{\277\243\034\320\"}b\243?%\21470\374+\262\277\277\316\273\005H\217\241?\026_\221\314\212\362\221\277pp\337i/e\304?a\007\374\245g0\241?c\235*]\342h\304\277\"\256\002\216\314\275\301\277L\360u\247\257/\276\277\340 \321Q\262\254\323?\237WwE\034r\221\277\300\274\0138\372\014\262?w\006\222\315\030.\324\277FMI\347\213\263\251?\272l\3424\217\335\304?\37179Rb\200\304\277\010,\313\367\337<\251?;rW\2172\202\300?\000T\245\376ca\230?(r\003\2559P\302\277#|\253F\3666\254?\213j\377n\007\364\264?\304I$\354\211\256\233?\352\232\333\302\271\341\256\277\272b\301\257U\201\313\277\220L\r\315H\250\306\277\007M\313KR\206\267?\352r\256)\017\017\227?LT\300\022\376S\230?):%\332\245\210\315\277\260\202\363\235\236\241\267\277\274\035`\342\304\221\223?G\330\270W;\261\276?\2154\310\374\206T\272?\017\020\220IE\334\302\277g\301\202\260\246\344\271?X+\"\253\\\002\312\277\323{\353\357o+\267?&\257\363\306\032\225z?\335\375\336\352Jj\253?\005}?\023\331\200\262\277\207\213>x\231l\263?\013\235\016mu\030\302\277\367\237u\255\240\177\242?\265\357P\020f\216\315?\365\010\333x6U\263?v\020p\321\201\242\240?)\343@\256\273\240\317\277|_E\315\225R\313?,RE\214\013\272\243\277T\337\244\314\327n`\277&\017W\211\022\377\271\277\354\361\241\035]\341\251?\327\017%\340\n,\241?y\014\246\234?\246\301?(\273\240!\317\002\236?\300k3\330\034\330\246\277rD7\002(N\322?X\030\241\251\357\313\302?\036\363g\034\007\345\264?s@\310tz\334\251?\314\347\374\300Po\306\277\346\250hC7\023\316\277\315\017\222\202;\373\254\277\244\355\237\006\351\334\244?\354\017\007^}}\270?\367\255\330\036\200P\266\277\007\020\001_I\352\243\277|\311_\340/\215\266?\216@g\024\272\311\241?\362W07\274\003\254?\035`\"*\261T\277\277-S\271\241\250e\331\277\224%q\003\344@\240\277\027j\250\2601\341\254\277-\266\232\253\220\312\313?\222At\0179\253\321?\207\247\353\354\260\356\231?7$8\312z\246\220\277\206\010\241\343\260\006\311\277\003\225|\245\014\332\253\277\347\371\364a\330ER\2776\267\206*O`\200?\250\031.4a\243\216?\026\340\365\026\343\233\216?\026\014J\026\344}\203\277\024\200C\n%\331\206?r\324;<\377\321\201?\277\022Im\317}\312?v\031<g.\230i\277\202\353\314Q\032\007\271\277\373\007L\366;\037\267?S\336\305\322\311\265\205?\"\363\314\n\005H\305?\26223\to\023\177\277\222\"T0C\243\305\277\205\341Z\3541G\265?\305\037N{+\331\253\277E\250\326\267\361\030\274?F$\360bn;\265\277\207Z\273\370Q\267\252\277\362c\255\224\347\\\242\277\275\366\373\226\007K\266\277#\346\335\354\355Q\320?\206*\304\226 \014\311\277\032\0200\205\034\372\307\277I\200\016\242\032\306\243?C;\322\244\351W\305?\231h\252\234\312\013\235?\'\246\311\t\227\251\241\277>p\353\270v\346\274?\370_\005\202\223 \250\277\244W$6\310\033\240\277t\034\343>x\034\257?\332piG\325f\270?\251Q\334\'\214\300\234?\240@U\204\335\372\276\277wR\206g\013m\260\277Mt\311\230\0357\225\277\354\022\312;\263c\247\277\346\204%\220\220v\234?*tP\333\331\267\265\277\315\177w\277\315}\266?\367\007\323WP\373\263\277\000\034\n*Z\366\324\277D\272\326\210\326\332{?\235\335Y\010L\215\266?\337\325\024tA\\\302\277\201\320\353C=\252\300?\236\203\" \354\032\265?g}\346\021g\220\210\277x7\234\"\2470\243\277|}\025\201s\007\273\277\212\310\007\263N7\223\277C\220\350\n]B\213\277c\003\351\211m\321\241\277j4r\257\331\227\245?\231\363\206\323\262\025\220?\035G(\333\006\370\273?\234\016V\242\347&\243\277ji\022h\000\266\254\277\367\211\2161\235L\266?}\n\220\376\371\263\272?\253\031\215H\006_\222\2770`\244\367\033\323\253?j5\266@\377\254\270\277r\201)\240\313\213\241\277n\2507\366jB\263? \204C\3534\007\264?\036\221\210\322\202\260\300\277\r:\200\251\003\303\211?\"\245\254\034\317\252\272\277\207tT\000\234P\211?\205\007\230,\031\346\242\277>\2727\303\313\230\243?\016\200\361\343+=\330?\203\234;\307\271Z\311\277\376\r\262D\002U\223?\317\237s\236\372\357\303\277\347\250\200w\307h\260\277\010\025\030\306f\225\303?V\345r\2650;\240\277i\331\363a/\331\202?\363\360\265\312\010\274\300\277\331\332\226\265\353\010\224?\265\332q\034\333n\265?\352~\214\261\206\020\261\277\nC\213D;^\264?\240\272\270\304\037\\\307?\276\344]\270\035\245\320\277\216\204\271\240P\371\325\277\331\221&S2\310\244\277\362g\035,\230\024\270?\314\021i|\017\356\322?\242\346iCcT\231\277WI}B\223W\267?\264e!\272.8\204\277h0\204\337\362c\267\277\250\007sFuw\246\277?f\323>sQ\254?\334mw\014\365\324\210\277\304\230\254A\206\013\236\277!\276\331O\010S\265?\224sihm\340\251\277\250YE\3144>\272\277h\355/\362\317T\251\277L\327\342[\325\027\223\277^\325\ng\331@\270\277iJ\232\004\037H\216\277\324\370$T\2426\265?T!-\266\226*\271\277\251\372I\267,\235\321??!\375PG:\310\277\027\340\t\277\275\022\237\277\316s\'V\353\n\263\277\306Hk1,\227i?\316M\270\342\t\220\263\277\352\020\314\227\305s\220\277\356\257\017>\341M\324\277ig\335}\0047\222\277\255Y\214*m\217\245?\376+\2279\353\202\316\277\305\022\'N\311\240\305\277%ItUF\004\324\277\276X\003l\313\353\266\2771<bK5\"\313\277\017g\203D<t\245\277\\\341\000\337\366\177\272\277quv\036\326\355\251?i\354\352\223\2016\270\277(-\200\004\351\211\277?\251\223\240\346#w\235?\210\222\005-\311$\261\277\360w\343Ak\341\303\277\270\265u\276\367\326\301\277\020x\014\212[;\261\277N\243\312\224;(\256\277\026\236[n$\211\205\277\3716c\214x\013\302\277\324\300\326\033Y\271\315?Xw\254\025\337\006\232\277O\306\357L\264%\260?d\204\022J\006.\326?\371\317l\013o\334\300?\300$\200\30106\251\277\373\315\177\312\226\245\246?TU8S?K\262\277\346X\336\340\370\232\223\277O\276\211\\\2768\301\277\377\010\320\344q\377\276\277|{jh_\307\255\277\021\276?\360EY\207?\260\3722\026o\254\260?F\215#\252\252v\262?\331/\341#\034\217\207?\362\356\250\032\223\320\266\277\237Wf \353\010\312?3\242F\022%\334\302?\213\36621\252G\316\277N\224\366\253\235m\251?\314\215[\323\351lv\277\036\350*\352\253Vw\277^\336\247\317\273$\303\277\304Fm<\343\353\322? -]O\200A\262?M\037\365/\344\235x\277P\267\323\205\3572\275\277\036\227\364\351ds\277?\237E\225\007\224\202\245?\212\246\334\201\346d\256\277\006\320\303\224\333\253\312\277\367\317\354-\331\321\273\277z\372\022\352\212\320[\277Miw\304Ww\271?\201\364\355\313\253\241\277\277\204\022\321f.ue?\354Q\021\221\007\323\277?\267\207\202UX\320\320?\302\303\365\034\240\032\321\277mz\3625\344\023\262?bX\316\265\203e\267?\302\023\r\336\304\274\247?j\240\314\200\360\217\264\277\215\307jN\345f\202?\355(\255\311\272\377\324?\355\005\356\372\337\272\305\277?j;\025z\021\276\277\264B\377\0055\205\310\277MZ\177)\337h\265\277\373\223\377\363\340\357\246?\014\265m\006 \256\254?8&1\375N\252\302?\003\265\244A,W\244?<|/\273\256\027\310\2770\235O\034\252\260\244\277\365\322\237\027c\346\301?R\335d[\333\315\235?\302\204\t\343V\300\273\277\035\354tv\325\204\314\277\272!T\223q\316\263?\017\020\010\341\215\t\272\277\017\213\206Q\016c\274?&t\304\335A!\272?\217P\352\335i%p?\334\351\201y\023J\232\277@\344\374\177J/\242\277/,Q\017\361\"\222\277\036T}\316\203\031\251?\245\337\"U\223\010\225\277\243d\235I\276\033\306\277\237\336\231\240\267l\246\277\024\300\244\232\024p\262\277\314:y\2678\333\254?\2677\370\261\256T\271\277q\034\353\235\373\003\264?9Sd\303\325w\272\277\360+\237uw\006\312?^\005\r\277\264/x\277\235l\242\211\311h\203\277ld[M\232a\201?\227\342/TF\307\243\277o\216\332\300\237\374\246\277\320\014I\206\225(\217?\024\277\344G\314\235\301?MI\2503\234\334\215\277\251x\337\335\330\370\270\277\241\200r\376K\241\312?\335>\206\271<\273\311\277c\005w\321\232Ar\277.n\255lY/\264\277\031d\203_\370\375\302\277\016Y\332=I\215\250?\344\202z\225&\004\306\277\t\203\001 \236\217\261?\220\207\250\224\t\267\274?\270.\017\352\240g\277?\320\350<\313\250x\243\277\320ZNae\241m?\023\037\032\274\374\363\257\277\r\313\t\027\211h\204?\014\3027\254\036Y\307\277\006\227\377HL\274\230\277\027\235@\241\216\277\261\277\250\032yI\017%\245\277M\232)\355\331z\301\277\353_\240\216\031p\264\277P\350r\231\337\227\223\277\362Jn\253\030\223\241?\311\352\002a\004\020\273?JT\363\026Jm\261?\036\016\300\363;\'\312\277\365\331\302\357V\216\321\277W\372\365\320\324<\231?%\242\276\366\202\266\270?H\370\205\310b\034\207\277\333\236\244\230d\030\233?__Oj\342\325\252\277/\252y\214\262:\267?y4\370\204GU\263?\217\350\270f\335\231\246?h\374\315\332A1\271?\300\332\354\352\311\325\275?\025\030d\375#\221\313?\021\354\262\364\210\242\233?G\356!(\317H\257\277\014Ap?\327\310\302?\205\n-\\6\332j\277~JY));\264\277\020\032Cv\024\337\276\277z|~4;v\247\277\016\354L\376C\023\263?\277g5\325\356\'\320?\312\315\260q,\231\262\277\325e\344\2725\212\301?\3312\002\355u\315\247\277\217\251\251f!\017\277\277?\031\303$A\216\227\277r9,\026U\305\301\277\273\230\344\334\207X\215\277t3\312-{N\242?\271\261n\3427\350\236?#+\316\373\360W\262?\270e\372\214\315l\263\277\345\245\267\3652\256\260?\301\022$\366\023\r\265?\"\262\"\254%\336\255\277\253B,\334\377{\304\277\356!G\221(\214\251\277\227Io\270k\201\232?gM\177\215\343\300\302?\234\247~^[&\251?\370\317/\334\023\303s?\371\353\016\202u\352\302?G=K\0014f\262\277>\234 \232<t\216?\261\027YwM\261\251?{\r\316\360\205\244\322?\317L\371T\377)\243\277\222\003\\\374\244\266\277\277\325\261\377\035a\036\305?\315XX\371\332\004\323\277\023\245\371l\355\310\262?F\266^\375\315\204\250?)\263tk\372\352\302\277\n\334\017(\020\000\242\277\331\275rH\255\'\262\277\321\337\317J\314\017\230?i:b\274d\300\306?\201b\234\230{\367\270?y \257l6K\252\277\202\025\256\260~\350\274?\200\337x\021\354q\302?0\305\035L\351\331\240?\256~\366b\353>\261?\327\213\251\21441\330\277\325\234\010ESL\241\277\025T|C\231\033\276?JQ\206\307\340\232\320\277\034\307\227Y\377\024\202?\312\200L\010\217N\273\277R\313\327\010\355\211\253\277\";\001@\351\271\254?D\314\274\003\260\342\202\277\3736\216\200\330n\264?\223{5\004k\210\303\277\302\322!h\232d\270?\346\242\363\"f\367\250\277\317\000*2L\356\274?!Vl:\335\324\277\277\250kBl\003f\244?\256H|\365\303[\205?\274\026$?\227\257q\277y\303_\322\365\337\253?\365b\303,U\247\264\277\004\tB\014\300\273\270\277!b\214\244\367\032\317\277niA\220\375\243\250\277\001\243\367\214\340\333\244\277\267\266\311\232\025b\303?\203R\227\272u^x?\343\361\311\212Dh\216\277\315\372\027\344u\242\304?\235Vg\214\347\356\305?\251f\300e\025\'\254?%\271\033R\006\325\254\277\351/\207\002\212c\321?\347\336_\\6\307\254?\360\343\374\023\007\305w?\210\016\036\351\200\373\273?\364\212\372<\274\255\323?&#\257\216g\314\222?l\351\245_\361e\300?Vl\331\367!\272\254?IM\217]\332\036\227?\306+\247\351e\307\302\277F\312\235.:\272\220\277\0133\020T\233q\227\277\224P\246\222\315\266u?\252beE\262\233\245?{\006\242\261\030\316\311\277x>{f\324T\230?\272\360\\\270p\223\257?#\313;\327\343\246\220\277\317\"\364\212\341\366\324?(%\3039\007\245\243?+/\254J\244\036\306?[\363\344\031\035\277\300\277\241=+\257\214\306@\277\337\352;\300\254B\310\277\257E f\240t\302?\363\205\027\233n\232\244\277\204\237\375\215F2\250\277\334D\331\266~\345\274?p\004\352\276\022\003\306?G\006\223,Wm\250?%\235\376n\206-\264?3\356~\026\372\031\255\277\250\333\231x\031\316\304\277_B\3432[@\232?\300Q3-\235?\242\277\211\326\326\016b\251\254\277>\274\031[\014`\251?z\324\250\220\335g\275?v\206g\023\260\246z?M\275bd\036$\275\277]\333\031\020\215 \203?r7buX\225\242?F&\364\344\"\010\255?\257\310E\301\362z\271?\315\021%~\'\n\251\277\356m\023\331\327\353\210\277=\316\177\233\213\241\277?\307 ;4ku\231\277\320\345\313\373qQ\245\2777D\001F\310:_\277X\373\250-\222&\212\277\371\026\230\233}-\302?\0130$\323\277y\321\277\273\017u\356\225Z\253?\023\242\252\034D+\255\277\352\251\\n\342\033\316\277\020o+8\354Vu?O\264\007\254a9\272?xf\037d\363K\277\277\355\222\021V\"\324\230?\340/\275\220\013f\307\277:\3754Bh\274\251\277\002xP\335\r\035\277\277\253\242Wh.\013\200\277\2540\200\333\336\335\274?\344\025m\255 \314\241?m\244$\177\005`\307?\342U!k\200w\320?|F\277\"\320\030\273\277\001E[M\320a\247\277\253\332)\361\030\272\244\277%{\264B\020^\301\277\342\300\023Za\017\222?o\266H\340[i\243?`\362\310\357\247C\232?\027\356\222\035\245\325\223\277#h\353\0339\322\246?\325Oi\321X\003\312\277l\251\267\035\240\301Q\277\354;\233\205\346\351\267?\203(`\33240\245?\035\261\016lWE\275\277O\017l\225\023\034\210?\2143#\246DD\222\277\306\351\366\261\266\024\272?DH\355\317\230\031\266?D\275 \341\361\256\235\277\266\346\322\217\306\274\303?V@\021f\347\001\244\277\'_\324f\350\335\222\277\353\3323V\007 \276\277k/\013\026\302\356\250?\313\372Le\024\354g\277\346\351\220\204ac\256\277\320+\004\013\001\266\254?\r)&\276\303\256\264?B\366h\"r9\253?\353\274\017\020E\267\270?;\270_\"!t\275\277\222\016H\330\376(\260?A\020\237\301\252\311d?\335\264\375}\217W\242?2*dJ\016\221\242\277\241\352\203M\367\301\241\277\305\376G&\275\244\310\277y\2340^\017R\203?\347\217\t\t\254\300\254?D\214\036\322\212\350\214?)\310`\"\021L\246?\247\305a\242E6\305\277ZeP\223\202\353\272?r\266\326\212D\330\237\277\366\334\200\260%\303\261?\321\221\3001\265oi\277\200I\346\200\360\200\262\277\275\204\341E\351\377G?y\302\300\360\202\215\303\277n\212\215!cy\261?\352}\030\377\242.\307?x\214\363&Wb\311?\t\263\003\336d\336\241?bFe \337!\306\277\337S\227\313\200\376\263\277\310\267A\335\230\330\267?\365oe%J1\302?\273\322\327\275\036.\242?Vq\324\201\247\214\314?+%\207\300\304\231\203?X\206CI\267\007\260?\363\354\227\t\033\267w\277 3c5\235\211\256?\313`\245\'\206n\204\277\363\2574\371\320m\221\277\330xD$\036\2776\277}\326(\330\3569\256\277\234\324c\326\277Q\310\277p\276\303\225\274\002\233\2775\215\256\314\212o\320?\020\355#>\006\207s?Ez\3270\343\262\254\2776b\332j\276\367c?\336\317\331\313V\373\251\277`\300\304\347\236;\241?\263\343:\244\237\365\252?\344\242\'\007o%\302?\224\364\244\3576\337\254?\251\221\317%\230\215\220?i\032\307\357\316\007\273?\276\270}\260\0206\276?\233\025N\331P\271\215\277\354\207[\266\352\275\264\277\275\013Q\310\016\233\260?\254D]s\255\300\273\277\326\253\212\2754\240\306\277Z\350^)\374\210\217\277\001\326\020\363c\331\224\277\201Z\0237r\351\266\277\307\321|xq\214\256?\201\t#\343\351B\263\277\266\034\300\231j\347\311?\225:\364i\346\342\244?\037\204W\354\017\233\260\277\310\373\321T[\267\216?\2777\261\177\\\221\300\277f\237\037\212\362d\264\277&\3714\352Ry\302\277\204\232+o\332\365\270?\207\306\315MM\217\271?\346J\023a\005\277\277?_\306\020E\351 \274?{\332\350\251\212\234\275?z\267\234\202\033\246\274\277\332\313\321\231\333\274\202\277\355\006\nB\240\300\206\277r\351o\314 \320\250?\237\272\022)\217\371\323?Q2\274\324i\356\221?\346t\177\274\3278\274\277\271\0253\234\276:\310?&i\226\020\305\241\242?,?\254\313\206\214\241?\223\252\241\362F8\374>6\314}X\235D\304\277\033\371\234\313\231`\254?0\236m}\'\255\264\277\271\177O\026Z\352\300\277\"uAZ\376zr\277\001\364\354\2229\235\213\277s\227\303{v \265?\212\023\033\372~\034\305?\310R\336j\304\215\214?#}QS\014\001\244?\033\222V\205\330\373\312?\334\342\225M\272\n\277\277\270\247?\265t\331\256?!T\242Vb\036\224\277\374\014P/\004\236\303\2770\336)2^P\276?M\264$\352o\217\247\277\201\261\345\212\275\201\252\277\256\267 z\225\333\254\277\026\316\256c\313}\222?S\322\'\325\332\372\213\277\343\216}\001\273W\261\277\376\226B\3463\231\257?\014bP<\376\020\263?\214\331\017Nl\215\312\277*\223\021\324\371\317\317\277\224\333\325O>~\220?\031;\340\242\305^\264?\277\365\311\341)u\226\2779ra\010\354c}\277\010\272\232\260\0178\253\277\366L\212\365\266\247\303?\346u-W\264?\223?;\2206\312&\201\205\277 VwA1\201\235?8\201\256\377\002]\305?\313\000x.Bug?\216\360\230\301gA\260\277)7\262B9\324r?\372\207N\242f\003\321\277\212\334\350\315;\234\266\277\210\031\241\235\246\270\311?\240\340T/\337\267\304\277^T1\256\224\323\243?\257\347&<\364a|?\332\277?@\370\026\270?\010\273\332\304\n2\274?}d\007\334b\316\261\277\347\361\r\307\353\211\271?\321\201W\322\227\270\237?D\276\373\240\231\315g?F\307\200\307\0311\236\277%h\245.\327\003\277\277\335\233 \275\216\361d?\377\355\376\202[c\252\277\325\036\203\204\205\340\232?\350\311\335\253\243\222\270?\305,\310\323Uz\321\2773|\022w\313%\215\277\370\002\202\001\276p\240?\361\211}(X\357\277\277K\204\363\207\"Y\272\277]9Y\367\262\020\204\277F\324\224-\322\001\302\277L3*E\314\352\267\277\365\201k\233\271\245\205?3\"\251\243\tk\306\277\233\313]\232\265T\320?\200\214\221\031\332\022\261\277\374\375\247\304\376\331\266?\363v\323k\227S\240\277\331\317\001E\'\204\236?j\373\213\261MT\261\277\016\270\310\330\232\253\221\277\312o\215\216<\321\242\277\371A\226\352\'5\243?\177[\'\275\307\211\306\277\231\241\215\003\347\002\302\277\373\267\336\300ag\304\277QC_\3306\363\254?\303\265@B\312\206\264?\"h\202\334\004\224\302?\022|\323&\303\275\271?\363X\034?\371\222\321\277\312\311\377Nho\301\277\010u>\221\270\250\262?\240\032b\035\231\303\261?h:\273P\237\322\225\277\000S\274:\345>\247\277\377\243\206\216\371\030\260?r/\203\312C\277\262?T?\346\265P\332\235?\030\302j\340W\270\245\277\r\311\201cv1\243\277\002\343a\237EX\313?\264?\351\271\215\207\241\277V\256\243\240\306\261\302?@\032\233\316>\307\277\277b)$\277:/\320?\347\211\022\203\316\251\302\277\"\206\2242\266\017\263\277\227/\334E \002\273\277A~7=\013\320\210?\373\213\306\372\364\327\271?\250\242c\2100\227\312\277\331\263o\354_\235o\277\030l\324;\037\r\264?\000\341.\244\337\365\273?8-\004\0231\022\217?\243s\254\037\234\211\301\277xBV\013\023\301\262?y\270\203\251\223\236\270\277\356\252\217\362\320\301\261\277n\030*\016,\362\312?\341J\023F\026\310\267?\245\207_\361\004\273\264\277\254\371k\013\263\242}\277\216\234\330O\347\337\271?\001\241\200\276\372\307\261?\225\212\357\372\312\020\266?K6\300\252\2607\264?\240Gc\364\375\004\274\277\014\264\253\315\231\035\312\277?\177\272\245a\344\304?\027(;\031\260\357\237\277\342:\376vt\243\303?/\025D\335\242\262\322\277\022\005$y\0309\267?\213#\205Z \\\227\277y\276\237\017\035\177\246\2771q\315\036\037\220\257\2777\024\323\024Q$x\277\232\243\202\321d`\232\277\274\007\366W|\020\304?\235F:\241\014\364\253\277p\207Qn\t,\302\277\030U\353{\320\243\252?2bp\321\022\270\317?5a\366\344@\207\301?\346!\035\336\251\024\246?\032\350\034\210;\265x?\215P\301(\352\020\242?GC(\345\035|\270?\346\252\277r*#\250?q\216\206\014\264\032\312\277\374\315\330\241\\\311\276\277\313\304\020\020\206X\277?\004\365\252f \000\253\277\005\025\350\360\301\360\247\277\32621\206\312\276\275?\227\273b\263n/\320?\333\307X\026 \023\320\277\257\032\021\222m_{\277\202\341/\247+\246\217\277\351\237]\022q\203\264\277t\364 :;\035\307?\204\371\346\217d\221\177\277\270_\365\223\2222\301?*\rh\374dq\313\277\374\217\004^Ps\237\277\316\354\225\276\350\252\240\277\362\222\203\3657i\300\277\3613\355J\215X\245\2771\224\256&x\237\262\277\027C\216y\272{\267?\266\363}\343(\332\300?\267\035\335\323\251Q\251\277G\315\243W\352\371i?\266}\001k\352S\246\277\327\33459\3649\302\277C\312SpB\350\257?\227a\016\177\204\000\273\277\255(m\242I\314\310\277\224\241\023g\315\305\266?[\331I\r\037\242\300?\016/\302>\034\244\272\277\367\222uW\3228\206?\"\023\354\324Y\331\303\277p\342\331l(2\315?\224\230&\267\215\364\325?\270\304\236\271\201\305\252?\370\350,\313\360\266\303\277\317\225\351M(@\307\2777\273\260\003\364\\\231\277\001\217\250I\204\300\247\277\375\344\246\323\336\345^\277\216\253\205(\0037\305\277j\221\260Q\347\314\254?I\214\347[\235\356\324\277\254W:\315=G\271\277\006\233\315\250l#\301?\033\325\340\203\215\246\272?\005\235E\305 \366\263?}=\347g\013\340\302\277\265\223@\316\004\032\242\277\360R\302t^\227\271?\343%\3749\374\033\201\277\316.T\364S\032\302?+\'\266gY\345\267?\2178,\025\301\364\231\277\302\342l\202\347\004\241\277@\\\373\366D\014\262?h\263\202\241>\223\226?\317L\tXe\305\312\277\327\345n,Z#\227?\\\035\'\376A\253\253?o\351\331\270\334\"\316\277&%\266\023K\'\347?J\251\026\347T\010\240?u\211Q\014\212_\311\277\250l\"v\273b\250\277G\222\352o#\377\206\277\204d\361\0004%\236\277\254\252L\034\212\323\304?\237f\240\032\342s\241?C+\022\226c\372\204?]V\377\034\217\3336\277;J\014\231\312\016\317\277\037\273\273\347\263\252\300?\266y\2709\033\361\243?3D\004\335\353D\275\277\202+\216\027\310\t\317?mbB\027\214\014\213?\257\225N\301\227\332\213?yw\216\270\250\362\304\277\261\264\273\352\t\354\260\277\2057{\246\307Y\263?\220!\334\252\010?\302\277\267\26712\372\251\310?\2551\005\007\261$\214\277\276\205\323\005F\257\227\277\240\223\377\243<*\266\277:\363\322\2074\203\244?\261\022\333\327r\326\261\277\345\010\335\261\002\000\265?\353\254\357P\324\376\242?\323\225\205\023\365z\223\277\211@zw\035\362\321\277Qh>6\243,\305?\n\r.\034L`\257\277\016#\3259\014F\203?\0103\2258v\020\210?_\302\253\244J\220\235\277x\225\376\255\247\266\255\277\275\361\004\374\336}\203\277\370\235\205\237\351J\233\277\301\224\240\306yX\204\277\300a=2\002\272\251?6v\014\310\263\336\226\277\307?\005r\212 \320?\007u\342\362K\032\261\277\273\\xD\035=\231?\377\rx\026\260P\271\2774\355X\301\265\300\272?o\257\230\017\356c\303\277m\344\225\245\016\234\267?\022\363\307H\276\253\224\277<\034\247\302%\373\221?po\0109\203\223\303?h\006\014y\203\261\311\277\234U\371/\367\320t?\032\021\345\225\373\007x?EC\350fq\265\310\277\tv\255W\032L\320?\010\025\374\375pvs\277\260\313}\020X7\307?\032\275\031\374rq\244\277Vk\263\366\311\276\311?\220\'\237z\030\223\311\277\r\377\275\"\320\347\252\277\033iI\017\252\276\272?`j\261\232\226P\263??\3211\206(\235\303\277\360\351\345\370z\321\235\277-\025\014\265\347E\234?\033\002\267\243\226>\266\277\213\322\313xI\364\260\277\024\226\260h\020&\327?\330\366\243\r\023\342\205?\252\227\242\r\221\036\241?i\247\300E\210\232\301\277\301\013\207\364Z\333\237?T\260\034\364Q\255\300?\310\244N\200\205*\242?\ra\255\357-\000\266\277D\252\307\354B\345&\277<[CB\323y\303?\257\373\004\362\247>\267\277\r4\246\320\265\273\260?\375\244s]\026\346\206\277_\302\327\265\324\201y?:\304\302P\\O\254\277;f\237\275\244\355\231?h\321O fB\303\277\236[\272\237Z\204\303?\363\005(\215\t)\234\277w\375\227\014\361\367\253?A\032\244pO\307\257?\014\201y\316\014j\304\277c\361\212;Zm\272\277L\353\316\306\337\005\231?\301\001r\302\377\005\303\277\335\343\304\373\007\217\272?\010,\225D\227\345\225?\037\320\237\031h*\264\277\002\221#\3166/\317?J-Z\267iW\240?i\376t=X\271\272\277\027\216\272\201\n\020\225?\312\271)\247\254O\226\277\2633\2106\377r\245\277\021\352\364\216\245\252m\277\367\2617\370C\255\272?\354\225\214\034\014l\256?\223\023R\245\246\324\267?v8\274s\261t\313\277\227\263\375\031\321\375\304\277p\021\010\335\301\016\213?B\335\355\351\017\036\267\277\240V\314\313\322m\261?\035\025F\352\226|p?\333_\255q.\316\221?\034\350\364RO\266\240\277^\365SQ\022\334\301?W\367~\372i\336\220?\215\327l\214\325X\307\277&n=\276\376\204\222\277D\231\020p\375\235\303?\312\031e\356\243)\266?\207r\007\211r\277\245\277L\230\350?\025\270\272?\005\331\331\345\034[\237?x`\242\325\374y\276?\203^\376\354T`\305?\305+\336@\326\nN?s\370\237 /\353\224?C\267\032\2113\307\304?q\003/\217g4\253\277\231\245\222\250|\254\262?v?dg\010\274\223?\"\036\032\017t@\306?w\024,x\036 \234\277jf\014\222\001\343\260?\364\021\264E\227\304\263\277T\017\205M\371\356\273?\200\033d\215\032\205\211\277\373\276\263\355#\272\240\277\371\215\024y\306_\307\277\264\303\032\246\241\365\242?b\331z%\275\325\255?\257~\211V\030\257\304\277\324\255\271&\324\215\260\277;\247c\236\373]\241\277B\245ziM\303\221?@7$\372t\361\321\277]2\003b\324\007\255?\325\341\336\013VP\265\277\373L\241\217\336\377\263?\204\225\\\310\356\203\313?\005\\\3044$\254\264?\200D\302\2654\272W\277z\273\252\027&\307\226\277a\223\307\372\311\213\251\277\211\357**\300\325\247?\2200\035(\337\"\241?K\224\274?\032\020\275\277\315\216\245e^Y\210?\'Y\027\374\256\323\311?j\344\013X\330\266\263\277\357\271\210\253\320\230\232\277\204\254\024\264\301\331r?3X,&P\265\263\277\345<\371\252D1\236\277\350\331\"\t\225g\272\277x\222=\336#\345g\277s\001\221bpo\302?j\331\r\0340-\233\277\353_\001\374\200@\300?\310r\204\0371;\317?z\356\025\235\351.\260?vX\273\373\357\026\235\277\014\r|\017\277c\257\277\263\346G\rQ\016\274?\007\356\033Q\235\346\246?\006\001\254\324\036\032\221?\343\354\362%\257\264\226\277\236f\000\330\000\211\234?\311(&\341\001\277\226\277Y\216H\210O\321\236\277T9\023\"\356\305\301\277\346\251/\322\0370\226\277kJ\330\371\301\243\220?T\227d\2452\237\245?L\213O\271\246\375\260?\014\001\360}w\231O\277\364\224P\272\227H\206\277G\326RK\002Y\302?\222\237)\261\007\246\250?\225I\265\205\366\275\307?\270\271\234\273p\354\274\277<Hl~\2652\270?\020\215n\202\335\345\311\277\023mz\331/\220\265?$\375\016e\261$\321\277\313\210X\231|\340\225?\221\365o\307GJ\277?\367\250j\r\016\002\230\277}xj:5j\300?\030\373m\335Nd\250?\311\022s\262\016\033\232\277\"E\205\342\222\371\322?\000\316o\010\255\303\250?\322*\300F\207)\224?\353\267\240\313\"1\224\277:\347\377\311r?\300?9\022_Q\013)\250\277\231j\321\375\366d\301?\331\327\322\"\020\341\257?\333\3733x7\014\256\277\'\0331O\376*\206\2777\030\233p}\257\253?KEr\010\206U\267?\217\020\006K\333\353\251?),X\177#Z\277?.r\301S\323\216\267\277\033\027c\005\333\343\237?bj\213\305.\010\223\277\274\221\020\370(\243\311?\\SuA2\013\301\2778\010\345\033\220\357\201?\013hp\337\343\344\313?\023V/\217\272k\336\277B\327\020\032=\242\271\277@}j\271\247\362\263\277f\346T\203\375\333\304?\3523*\335\366\r\263?\241\374\301\363\314\016\244?^\214\222\307\370\375\217\277!O\237\014\036\216\275\277\331\335\257\332\337w\247\277/pl\257A\342\251\277 -&\365\257;\321\277\303\312\203\t%B\270\277`\317\335\362\320\344\262\277\022\303\306n&-\277?\016*]\014\365\177\303?\232\351\312\200\207\203\225\277\333+\006\224\207Z\321?\342\010\274\377DI\301?\235\237\203\333U\310\245\277\242\344@M\017\331\225?\030\324\216b\243\207\302\277\246q\255D@\217\300\277W\211s\313\364\201\244?\227\371|2\325\203\302?\000\217E2\240D\274?R\252\177\223\200\275\224?*V\215\203$\254\247?\345\253N\334\250^\306\277\275<\316\263\301f\254?f\035\342\311M\017\210??\026\343\256J\036\257\277Z\314yO\037\275\310\277\263\367\023E\221\256\301?Bf~y\215r\262\277\215\203\025\000\314\362h\277\326\260\332\260\244\'\265\277\331V0\262A\037\207?G\337\316\271(\013\256?\247q\016\216\324\013\252?\302Q\n\n+\300\252?\247\000\303K\201\325\231?\271\3372\256h\242\252\277\237\336|\'@l8?\271\305\262\231Z=S\2773\312\341\221\313=\310?_>A\215\223S\306\277Vg\345t\330\360\254?\352\244\243\216S\277\232\277`\302\275*\336\232\244\277\373;F\204\177H\311\277k\375gA\323T\245\277,\036HzR\374\261?i\036#\377\036H\264?d\254\321\274\0335\267?\250\266\231h\230\255q\277\250\366L\225ne[\277\007\260\000x\244M\263?\270\254]\2135F\235\277u\213c\234\260\335\221?\265\2413\303\311\n\272?%\354(\014\000\313\213\277/\026\253j\001\254\260\277/@n\212\372\227\256?\037Z\323\006|G\326?\037\2436\340\034\324\254\277\376\207\241js\202\271\277\245\374\254\305-\224\321?\016\256\215!\311\321\307\277#\224|\n\272\305\304\277\377\355\212\017\232\332\304\2778)l\222\335W\235\277\264\263\267\342\367|\250\277\210\346\2363Li\262\277\320J\312\227I\246\270?\342\263\031^\005C\263?\030nI\n_\231\302?\240P~T\033^\276\277&S-\3572\304\260\277\033\254c\342|\211\242?Y\235\205\274\344\213\252?t\336\326e\274\226\266\277\300\347\315\227\240%\227?\345\370\267N\023\360\223?\337\347K\225#\210\321?E\251\177\307\330|\262?\321\247\330nz\337\260?\276\330,sx\177y?\374[\215\330\276\244\300\277\2407\223\322\316\355\312\277\262*\t9T\230\253?[\024\314 #\365\236\277\313\242\334\272\r\236~?\373HG)c^\234\277\323\204\002\216\326\225\243?\375\026W\370H\000\257?e\353\324\317Jy\254?j\300\360\254)k\260?=jh\246-\006\215\277\312\376\001?}\345\240?h\363\035\372\027\\\221?8\221\340R\327\270\267?\320\372\371\'#\027\234\277/\335\373j\200\033\241?\265\366\nhr\033\245\277\020\300\315T\367;\245\277\014\337z\377\021\307\317?\314\273\016\032y\361\251?\307\221\252\231\330T\272?~R\342\313.~\311\277\374\347\353\232\247G\231\277{UYj\247\234\271?\016\232\337\n`\031\265\277\n\345bl\rY\251? Z\357\372\207\027\273\277\352\234,\302\3034\250\277\345\345\245\020\252\003\251?,^\336\364)2v\277\216\266I\301\251I\246?\347:\341\336\'3\236?\267\335Ou\306[\230?\276\343\341\275\275\256\204\277\201\334\305\007\024m\245\277\2038\302]\r\231\266?y\000#\311\355\365\243?\013\220\366j>\010\260\277\313\'\303\327W\361\310?\024\r\005\177a\021g\277\206\'Ns\371x\275\277\226\307a\312;n\262\277\036($(\213 \315?\023\224fr\251\353\274\277\323\224k\342\035&\302?\213\2138\337\327)\300?i\005\3145\023i\257\277\"6P\r\372^\262\277e\017.\003\004N\303\277\262\367\021S{\026\250?\2436\214\n=D\226?\203:\016\225\232\247\251?\003g\246\344g\304\240\277\035uX[w\351\250\277j)\2217\231Y\246\277\014].Lyjr\277\007W\231\234\200@\310\277\242\220v\375$*\257?\222E\025\3122\310\253?\014zw~\2376\256\277v\035\231\273\373D\306\277k\360\335\340\300\323\246\277c\346\351\304\025\303\245\277\032\025|\316\310u\307?\245~\223\025\323A\224\277m|\355\227\355Q\303\277s\340\272On\200\271?\2105\341\023r_\265?\255]M\310)t\261?\205\027\335p\367q\212\277Y\031\003]\234j\315?\205\022N\262\002\254\264\277\246tz\271\242\227\276\277\307%\310\227\241?\263\2776\324\002h\270\212s\277^\254\231w\207\230\252?*\336\025\177\220\004\275\277\213]\263\276\3422\310\277J,\'?f8\263\277_\212\313V\350\267\271?\177m\377\313FH\313?a\362\013\203~\333\326\277\377\310\036\3116\376\254?\230\203\n\246\375x\267\277\205W\\\230j/\252?\256\361\337\275\246\200m?\314X\220\232\304\243\260?+\304\247s\264\333\272\277F>$\327\272\006\221\277\373\266\2750V\023}\277\363\307\244Y%\215\305\277\317\363\t\311`/\311\277!\204\370O\233\245\230?\211\346\275MZ[\240\277k\312\317\253~#\335?\305\341\002X\266\263\272?\354y\212\370\332\300k\277eP1\033\262c\313\277\215\347\206\261o\374\272?\242\363\252\013\365_\201?>\377\365\273TU\227?\024#G\215\310\271\261?\302\341Q\"\2413\227\277\020\005\"\221\260\266\366\276Y\006- \023\353\252?dh?//B\241?\336W\333@N\266\276?8\324\336 \321G\273?]3\302\235PL\271\277\277\032\321\313\367\267\324\277B\260\263]\275\334\222?-h/uM\005\301\277\2119i\260\325\206\260\277\302\341\332\333\356\374\263?\302Py\310\321&\234\277\224\026IOv\261\265?\353_3\270S\336\241\277O\3610\236\334\014\261?\264\0332\360!\260\272?\230\263\036wd\026{\277\234s\027\366\356\373y?F9\200Q\207=\242?r*+i:R\323?\300\304\255\335\226u\321\277\021K$c?\222\246\277\271h\320Fq\315\241\277\240\375\364\245-I\241?\025\300O\343A\375\267\277\013\317\013\350\303\205\260?\336\263Q\037\317|\276?V~\035\230\332\000\256?\214\340\212\204i\004\260\277\232\013Azt\207\266?\227\'\031\177\263f\260\277v\264\021\253=o\222\277\265,\210\031\226\221\231?\n\004F\370\201,\277?q\366-(\267`\203?-\216R\326\277\222)\277\355\346s\302G\371\255\277\371!\022r_\t\302\277W!\026\007\314@\270\277\220\250.I\343\202\302?\354\304\310\013|\247\254?g\004\'\367r\252\256?\260\271\342\333\340\022\302\277\331\321#LZ<\301?\\\211j)\210\027\276?zW\366P\021\307\271\277)\2701k\376\235\272\277\2667\377\314]u\311?\367G\"Y\344\220\250?\357@l\2512Q\274?\206\024\263\215\242N\226\277\375\273\3604\320T\224\277\327\254F_\221\272\313\277S\235;\275n\271\240\277\206w/\303\t^\236\277e\342\366\324\236A\262?\313\023\371\r\031\273\255\277avt\000J\260\237?k\024\254\031j\271\302\277\254\314\312\351\373\250\255?\r\272\353Z\'z\271?\373\364\240\237\214\023\323\277\022\037J\013\236T\274?\003R\303\252}\273h\277s\256:{\240o\261?[\033\350\213|\307\277?\273\312\32238%\243\277\267X\025\365\351hq?\037)O\355\213\'\261\277 \223\311\212\227\025R?0\003O\271\214\370\253?@\226\035\212T\272\267?\210\217\350\336\016\343\261?\307\204\035\214qY\250\277\333\342\002\341\265=u\277\252h\2437\017\333\240\277m9\355\307\003C\250?`l\364\360\251\323\253\277\326\t\241\013<\354\250?Vf\235\316b\261\254\277\023\210\370\024uf\277\277\000\356\274\025\217\267\242\277\227\213\242S\377\277i\277\013\234\363\367H\200\301\277\177\014\271\023\251\241\227?\353\343\207Z\010\367\222?9\303ku\373\277\321\277\336\342\033p\036\233\207?\352)\366zw\366\271?\345\305k\215\177J\277\277\203\245\206\333U\230\263?\246>yM\374C\230\277\034\216\352\236\n\326\266?\222pQ\2523\275\226?_\211\357\251\332}\273?LV\237\334\004j\315\277\356<4\216I,\303\277x*\337S\325|\264\277\233G6=\002\250\226\277\306\347<\220\204\326\260\277\357u+\235\305\236t\277\317\273\2752x\323\305?4\033\341\341\223\307\255?<x+~\220N\235?}#\306\225\342|\243\277q\351?\255\355Q\273?\312q\021\204\014\017\326?1j\3758h\353\222\277M\271E\202\366\026\306\277\232d\216\306\345\014\255?\343\252\222\227\227\361\253\277\032\355I\333\240\211\212\277\375\235\205H\363\027p\277/\305X@\337r\242?;[N{\250\220\271\277&\214Rz\245\271\262?|Ar\033\273\301\304\277\020Q\022\350\033\351\306?\320\351\227\342\306v\236?\245y\017\304\312\263\215?f\021\000\257(\010\225\277E_I\341_f\243\277\006\210\322a\246\312\302?5\345\034D\206T\262?\017\013\345\016\337[\306\277\022\302:k<R\220\277\\I\033\334\016\321\263?\376CH\215\243C\301\277_\344s\344\220&\273?\246\244\362\021#\016\251\277a\266\306\007\017;\263?_\032\335\324M0\330?Lh{Ay\035\315\277cB\\k\026\t\230?1\031\227z\341\016\252?8\205\216\014\260\337\302?\354\325%&\353 \316\277d\306\360\235\007\305\244\277\'\267\331\235\231\240:?X\3053*\370X\253\277\"W\037\034q\360\245\277\257\033|\232^\367\246?\323t_\335b%\246?\247\024!7\364t\246?O/\211W\354N\227?\006^-\270\244\223\257?\350\016u\257\310\253\263?\3641\247D\013e\222\277Tx\232\373\0351\272\277\n#\313\350\377o\253\277i\231;\242\036\326\236?\267\245G[\023\226\227\277\3238?h)m\243\277\360\301K\315;s\320\277\266;:|V\225\244?\374d\252\233x\245\243?\323\304#^&\242d\277\353\343!A\254\350\250?\356\014G6\364?\253\277j\345\033cu\'\272\277\325\344\366zY\375\264\277\225\324\265\357D\216\262?n\272\037dZ\300\203\277!\030\200\023Lt\256?\234\223\273\334\320s\274?\334I\206K@\336\272\277/\202\036\201|\266\230\277\220x\007\221\257S\323?\344b\352p\'k$?\345\234\277\027g\207\227\277\215\322Ex*d\234\277\345i-\243i\301\274\277\231\232\233\206\311\013\223?B\342\226r\207\353\312\277L_6h\253\005\265\2778E\317\201\335\364\252\277\317\006\\:\230\020\302?JH\263\274\347\321\206\277\221\037\331U\213;\310?\221B\361\330u\356\244\2773e\212\027\376\200\301?~\002*\323\'\374\321\277\026\347I\305\230\275{?\004\016w\313V\324\266\277U+\2420\030\177\233?\325\207\217\272,\201\252\277\237U\2157\'\214\302?\337?\271X[\350\224?\'-\204\254\323\027\277\277\252\031\267\276\033\177\266?x\334\001\270\325L\265\277\360\213J\354\032T\242?\021\316\355o\3147\300\277\267d\324\037\n,\223?\343\267\2774\027\030\263?\272\345\347c\023i\307?;W\367o\202\347\252?\330\0021C\002\355\274?7X\332^\002\200\254?\277\352\343\222+\337\256?Bc\350\022\344\274\252? *\\A>J\264\277k\236\272\344\200\034\307\277w2\240\306\220\263\306\277NW\202\273i\256\241?\200\237\271 \337\324\310?\245F\253{g\262\301\277\213f:^\365d\240?_\24406u\353\225\277\205x!\301\002>\266?R\354C\357\257n\242?\224\022v\267\r\361\300\277I\270\313\200N\026\317\2775\340\225\334\r\025\237\277b2\242\277\r\215\265?,\036y\215\343\270\316?z\023\324lJ5\265?B`\026\262\364\243\252?\341^/Mq\035\265?\211\0142\260\241(\303\277}\232\232\200e\025\315?c\r\tS$k\313\277U\301\343?\245K\255\277\024\014\205\2167\230\271\277!\375\305j\261j\264?\323\302\3555\361!\242\277\016\227T\232w/\307?\tE\315\030T\241\251?\\\014kU\177b\214?VI\001\231\212\362\304\277\231\227Y\310l\321]? \353\2451\305\033\240?\027\002\374\036\013Q\251?\233\035`\240\276v\211?\010\333\251\366\241\252x\277HM\206\025a\271\253\277\0144\315\035N`\264\277>7xakE\300\277=\365\323\211a\353\300?]\235\356\266B\005\226?\366e\021\325\245\376\274?\323\216\364\202\351>\266\277y \216\270\t\232\260?\005\203vg\230[\303?\242,}\260!\003\273\277\231NR\235\030y\272?\037\242NU.\236\310\277!\332\003\365\270\326\232?\271\365t\215pu\262\277\203\026\367\215\260\250\304?0\341T~\214d\240\277I\200b\377\270\255\267\277VG\241\272\224\005\243\277\261\274\320\\o$\246\277\377;Sj\302\301\300\277\334\221\245\352\264i\252?\331\367\250DH\334s\277\021-\251\nb\230\246?\277\203\335\370\030\237\250?#Yv\351E\276\211?&\310f\340\037\363\311\277C\022\033\020n\276\261?\367\257\337\227\242n\261?\003H{\200\256}\257?\250\361\224\026\242\246\303\277\034n\357\'\260\320\260\277\371<\\G\211(\252\277|M\307\241\202\260\267\277\221\036*3\257\357\266?8\032\014\205\215\\\251?|\366\t\007\227\024\246?H]\257}\3532\305\277\236e4\256\205\327\274?\242#0\242\231@\250?\300h\034\331?\336\271\277iT\225\330\223\242\312?\036\3232M(\301\307\277\255\031\265\rQI\261?\213\013\2568\351\241\220?\245\344\341\006\2530\227\277\nGl\250\326\220\243?\300\017!\000\027\013\300?\232\216\005Y\025\256\314\277\002\307\326)\270\377\244\2775\245\353\003\230}\274\277\206zLA6\260\307?\014r)\225\211\365\226\277\305,&\376B\274\210\277\'\343\340A\351\256\317?\306\304D\276\211%\245?7\363\014\216\301\203\267?e\032\255\262\347\'\211?_\314\266?\022\021\275?\364\270\370}\324\374\302?l,WZw\276\264\277\370a\215\333\367k\263\277\220Iz\005\265\320\243?\331\313!\251\351\210\252?PA\276\216\214?\263?c\"\324}\034f\325\277\264\223\315\307\260\023\275?\253=\264\002\213\025\260\277S\210\223\024Z\366\326\277\363}}\\i\376\260\277!\022\262w)l\241\277\215\020\330\247R\201\263?}\023\265\002\'g\257?\224\037k\200\013\013\177\277Q\345Q\257\207\363\214\277*e\222\304\244K\227?\263\021t/\215S\320\2772\365z\247\263\025\271?d\3530n\'\026\266?\324\360\301\366\353\215\260\277\006*\217\020\213{\304\277\364\247\366c\033\255\230?\351\2518\035\0244\263?\337\037C1\376,\271?\235:\332\333L\344\275\277\240\352-\031\214\353~?\321;d\244\216r\310?g\360z\336\032\206\312\277 \360\030\000t\236\261?\327nf*\032L\207\277[\025\277h=U\242\277\273\303\027\263\345\311_\277=GY\256\032\225\224\277\314\010\317\013\257$\326?\347kp\263\205I\310\277\2719T/d\351\232?\306\224\017\0027\030\224\277\301\332\325\357\223\010\265\277C\306#K0\233\260?\327\322\223n\r\364\260\277\3556]\002%\034N\277\314\023\326f\265\374\310?\374\354K\214\002\303\272\277\177P\251\335\327\254\271?\010\215#\2000\363\310\277\246`\241A9\337\267\277*\263x\220\r)\304?Z\005\365\277<X\302?\360\017\223\252\357l\341\277r\006\221\330\336\177\302\277\251\373\301\0366\360\222\277\003D\361\006XS\265\277]\261c\240\n\225\251\277\323/\rv\265\031\266\277x\262w\3433\224\260\277S\332r|7\007\310?\334\275\n\017\304{\257?fk\3759\224\346\275?\001\245\202\3711\317\262\277\311&\304(at\200\277\212\376\272\007\300\214\266\277\007\033 `q\020\303?\335\363\317\352C\203\306?N\237:\262FQ\224\277\2144\3470\341o\220?\215ST\337\3442\225\277\203\0255\nk\220\240\277\230\004\205U\372\266\262\2771)\251\001\327\364\315\277m\371E(\024\237\275\277S\307\324\246\177.\251?\321\035-vTu\266\277\214\327\025zD\227\301?\027\312S\271\302\315\262\277E\033\216\224\025\231\260?\030l\007\010\243Z\276?\005\n\274\350\327\263\312?\002\027\232\317\013D\251?\374t\247\226U\247\302?\362\316ZKw\227\263\277<R]O\232E\302\277 %\177<w\021\262\277\224\247\024\276\377:\265?\005\374eA\203\033\306\277\037\277\342\305\2719\272\277wS`<8M]?\275\321\255R\347\002\311?\251E\035\275\360\300\223?\331P\217\021\232\244\244\277\255j\330X\231\315\200\277\313\223\377\367\353\027\243\277\337\217\306\230\370\277\304\277\213\224\334\250\322\303\260\277\207L\241\nB\264\301?\224\321\242\255\375#\206?\317D\204\0246N\260?#\235\271(\362M\300\277\024w#\303\365:\201?\020_\231\357a\237\265?!/C+\025\305\251\277\"\250\354\200\004\252\274\277\366\326;}7`\260\277#\210\356\006\002\222\252\277\210\356.\355e|\267?\006\014\342\240 \265\306?2\217\264\034\306\201\256?\333r\276W\275>\304\2779\256b\350\255\021\224\277h>H\305\333\001\274?\374.\261\324\322 \306\277\352\352?\372\274\256\213\277j\251\0017Vt\305\277w\343=\222\\\360p?^\037\032`Y0\244\277\202~<\n\303\314\313\277\010X9vk\271\263\277\207\025\272\367_1\260\277\232\236\225\342\265\224\306?,\271\021-\332\236\310?\t\232o\376\363\021\215\277\367L\177\275\230\000\256?.k`\003|\323\272?\357T\250]2>\316?\274&\372\362\215\243\250?\013\313\'W\177\305\307?\253\302\022\223rI\233?\333ux;O\204\242?\336\331\253\312=\205\312\277g\231k\207d\026\236\277\320d\336\307x\023\241\277\302\\\277m\267\241\266?\027\224 \201R\177\246?=\237%K\3472\306\277q\301\257\352\272a\200\277\\1)o\227M\305?\277K\313\367mv\322?\326i%j\311\327\266\277\236<$7S\352\307\277b\245x0\273\363\235\277\304t\372\352X\272\300\277\323\342\255\021\021\020\227\277m\217\377\005\375\376\304?e Ym\307(\262?2\025\212;cb\205\277\250sp\203\331x\265\277\\\245?W{\343\323?\204\n\345\247\022\026W\277\327\027\003\013N\217\246\277@\266>$\3127\245?A\305S#\333n\275\277\016Nz\373j\325\302?\273\204\3765}Z\304?<\320XjF\320\270?\317\312\n\263\317o\303?\3161s\023\217\364\254?\305\261\316\260\253\364\241?\214\203\277\337\363\006\276?~\220{2/\276\241?)Y\027\212\020\302\210\2772(\342\343E\350\300\277\222\250|\270\201 \300\277\376\335\274JG\266\243?\372\n\371\310d\365\272\277\034&D\t\030S\303\277\\\n}\276kt\275\277\225\365D\001\303\221\230\277\020\371(\227mB\274?\374X]3\t\254\221\277\334\224\200\242\200Y\245\277h\266\307\025\006:\264?\020\026\323\332\370\210\250\277>k\177\355\301C\213?\354X\345\244\201\374\257\277h/\237\037\034\223\313?\302\342\206k\206;\254\277$Y\331\323\220\326\271\277\330q^\226\321\302\301?\265\223\333\262\016$t?~\322\r\274\2053r\277i\203\224sW\306\261\277\365\317#\301\265\000\177?EM\25526\270\240?\344\314\301L,\362\246\277e-\354\206V\261W\277 \241\335\204\r\352\250\277\241f/\224\251\177\247?\034\204\254\013\260\017\220?e|\275\337!\224\300\277\301\300\234+d\260\267?\367\201\217\337\000\310\223?\255\350\361\037\th\247?\200\033+n\263f\262?\227N\356\372L\332t\277s\3409\0137\325\273\277\003\336%\376{$\307?8x\325M\020\246\302?\351\356\336>/A\254?\035\330\245<\310\263\230?\352)\t\213\330\022\275\277/\036f\315\353\240\253\277\031P\214S\221U\227\277\225\306\340p\245\000\233\277%\261,9\230\367\307?\336\352\227\241~\226\220\277\371\267*\305\203\352\303\277]\020=$\327\237\245\277H\314Uf>\354\272\277\275r\330}\317>\240?/\235\311)\321\033\234\277D,\353l\324K\321\277B\007\206\366\\\232\265?`\232O\354\001\323\300?U\226K\367\320\211\312\277J\322Y\353R\334\300\277.\234\024\244t\254\245?\232/\211fd\360V?9\240\205I\304X\320\277\312\t@\004\341\244v\277\016F\340\2504\021\272?\177\311B\214:\002\272?19s\332\376\363\304\277\274\026\375\2000\252\271?m\207?*\240\275\310\277\'\303\315\310>3\315?r\343ea\257$\305?\347\257\317\036\n\213\262\277\247\363\231Q\016\322\261?\340\276\330UC\220\310?\006\351\252N\246\224\300\277\'\276\313_\300\372q?v\341XFM\000\312\277\371\251R\031\372\315\237\277-\315\246\304\213N\220\277 \210\336\226\234\371\304\277\357\321b\241\2475\243?UEU\327\273X\272\277\t\277\233\202\212\233\242? \032\305\332E\317\251\277\244\355\032\262\202\245\214\277h\035\3553\262\213\265?\357\007s\031E\005\310?9\321\020\2454m\231?2\206;)\217\036\257\277\374\343A\202h\203\201?\370\312\035V\353\001\325?tae\232x\277\245\277\317\323\r\247:u\257\277\'\261\317\275\215\356\313\277`n-\224\343v\320\277vx\356\025\351\026\276?&\213\264=\231i\313\277\351\354\272\351n\034\303?\264\315\n\344 \n\233?\256\304\031PCh\216\277\357\360\2608\337\333\311\277k\3608bQ\231\256?\323\205\3160\r\246\251?^\3413\206\033f\244?\217N\245\317\0023\273\277\"\333\250~Z\263\260\277|m0\210/@\302?\241\337\231s\203\345\315?K\221{np\241\261\277\230<q\032\224q\270\277P\325\025\324\300d\200?,w4t\033\236\260\277\260\006\307\245\371Y\264\2775\344j2u\001\242?\305\224\212[-\205\321?\335\354 \255\315\031\225\277\r\342!\311\304\264\250\277\312B\017V?\204\243\277\274=\330\252\210\252\270\277[\211\227\306Az\301\277}Jt\304d\350\246?\224\2474!\004\226\307?>81o6\374\201\277+\360\326E1\006\311?x\272\025\216]\002\263?\345[\227?V\201\250\277\203]@(!\264\227\277Ic\216\373\302b\270\277\220\371z\017Z\241\254?\210^\254\307\332\372k?C\354b0L\226G\277\231H\252\245\365M\301\277S\352\321\"\323\277o\277\226\010\207\220\213\247\242?W8\211B\215\331\177\277\221\'\202Z\255\247\314\277\224\243\361\224\202\023\304?X\014\3276L\216\246\277TU\263\221\340\303\252?\356\311\317\360\275\364\300\277J\237\324&\353Un\277@\255\217\274\262\336\257\277B\346\226;\252\357\255?\240\371\346\005\023:\310?\353\001\n\337I\021\202?)\261h\010B\245\301?D\337\252\272\363V\270\277\t\352\201\200\007\'\255?]q\232\2570\377\232\277\032\2234W\000\323\244\277$\034\276\207\232\343\244\277M\032%\267 \303\241\277\236\363\357\250\312q\246?\325K\202\024\2415\327\277\0066\225\243;\024\225?\255\302\342\213E\277z\277\224\233-0.\367\253\277\264W\365\343\320\267\306?#\212\320\3131-\271\2777%\202p\025}\261?u\222e\362\332)\310?L\234\213\3033\305\246\277\212\320\270\005)\324\207\277\254d\322\363\373}\276?\305\020\020\204\241\210\305?\262\267\004\037\325\223\270\277A?\2668\354\235\226\277]\221\254>\361v\326\277Yi\274d\352%\270\277\322M,v4\304\272\277-D\332\313x\375\246\277z\375\244\232BV\177\277\274\022\354\203\257C\305?\272\242\006$5)\240?\301\346\364\340%\335\221?\212\304\341*\217s\252?\221\236\251\324w\350\210?-\331\226\320\265o\205\277\026b|?\256a\262\277\001\272\240\311\271\023\265\277X\032\2726\363\376\251\277\353K\3144\312\254\314?\006\267\005\322\005A\250\277<|\320\344\327\202\261?\271\003\232\033{\004\231?\206}3\344?\326\234?\204F|o\331vv\277\221\t\300\266\020\276\235?\217W/\230^\341\253\277~\200\265\374q&\221\277v\222\256\246\210\307\274\277\273^|\376C\355\245?\223\277\233\356\021\341\303\277\022\252o\301\251$\241?_U\260F\007R\240?\270[oi\215\371\301?:?\365a1\201\246?\275\233Q]\004\320\300?\001\203T\327?\316\252?\205} \263$)\255\277\230\330T\354{\213\177\277\203h/\315\252\212\323\277/\003\021\261\026\215\276?z\2628q\247$\236?\251\231Z\234\374\217\256\277,\233#`!\005\311?d\223n0\177\035\264\277q\334\240\205Qd\250\277\r\311Ao\236\021\262\277\373VR\267\312\313\261\277\244\242\023\225\316,\206\277\032i\003\317\304\rZ\277\031:\001\026\337\255\323\277\370v\237\253\375A\253\277RX\352\364Gv\304?\2147/,\261\365t?l_\240,\336\357\320?\234\304+\215\272\212\270\277\277/4\215\353\235\250?\267\346\273\302;\241\301\277\201\240\322\003\330\024p\277(\356\242Gz\370\265\277^\234\246\214\347\001\252\277\245L\247\021\301\005\221?\007Od\377E\202u?\242\010X\200C\275\262?\271(\007\371\010\244\242\277\310k\237\327=G\262\277\203m9\306\331\333\300\277\251\321\261a\234\024\227\277\266\327R\365\201x\202\277\213\250\233\3446\372\271?\315A\354^cS\260?\2470\243\356\377\252\260\277\315\231tR\211m\266?\3353\326t\205\233\306?\350\200\363\023\221f\303?\252<_\035XY\325\277\276W\301\016\250\270\265?\036\307\277\025\000\032\315\277\364\014\207\317\214\013\263\277\255\353+\3077\315\261?\316\217\016\307\017\302n?\260#8w\362Y\205?/K\224\237\315\332\255\277\036@\213f\377nM?4KH\022\017^\302?\203\257\2610@\320\220\277.\366\316\351\210\261\236\2778\306\226\315\021\304\231\277\236ah\030\030|\257?l\270\210oO\375\275?\220\247\226\342(\267\275?\227\006\035\004\032\n\231?\213F\276\351\240\362\234\277\333\336\313\321\267\270\255?!\0051\206\253\341\242\277\350\007|\352\\\356\310?5\327\213\200n\273\241?\261?\004\025!\254%\277\332\201\002\270\266\\\242?\276OZf\371\023\223\277\242\023\275.S\336\300?%\351\003\327@u\256?\220\337\345\276\010\244\301?\355\227\372f\000\013\271\277:\242\242\366V1k?\306\016\346\362\301T\276\277\374\316\277\004mh\273?A\347\346\247\007O\263?\"3\216n,\263\254\277\323\230\316\345t\307\264?Q\265.\230\346E\321\277\371\214\252T@\324\262\277xwS\252\234cp?;\nH\336\326\005\276?}\367L\303\375\203\256?L\226{7^>\275?\005\002\202\264\216\375\305\277\2765\037\265B\274\222?\021\206\305\035\377>\263\277\207\261\234\213\377\245\225\277KY\347(\357\222\267?\255\202\013\021\n\220\266\277\'\377\245\306\331\351\244\277\241\327!\3446HY?\312\265e\356%d\301?7iu\227\311\252\245?\334\205bSO\335\234?t ma\036\026\306\277\255s^\277W\236\300?w\327\254\306BN\217?7\343|\371aN\225\277\342\024T\204u\247\202\277k\240\013\372;\375\246?\231s\033FDFk\277\344\320k\211\222Y\304?R\226\303\001\370\216\301?gN\204(wA\254\2772dR\335\207\243\255\277|>\n1\212o\271?\204\231\213\020\3353\257?P\224]\363\3105\263\277\337\224Y\242\320\207\267?o\224\262u\343r\321\277cq\333\223(\302\255\277\r\342hc\033\226\300?\317\024\265\240f\307\261\277\252-\210^\212\217\247\277Y\327\251\257\2556\274?oS\013\232\331\025\272?\267\027\206\002K\200\246?\225\030\t\205\211 \220?[:\344,\255\036\234\277@UU\200#\234\266\277\317\3157\200i\243\261\277W\2770\026\224-\267\277\211\007J%\371\207\233?\212R\245\275\320A\251?\235w\032\322\315u^\277\221e\241a)\231\247\277\314\346\254\337Ee\315?\222WF.dG\256\277\217\353\341\266\300\344\272?\341\373\211:7N\220?\0378t\327n\331\273\277~\\\022Z64\266\2779\231\271\376\365\307\274?d\361\206\350\367\243\234?\2124^T\005\354\273?\367\215\310\221\267\276\300?\212\326\251U\260\021\272\277\306\230D\225\020{\267?Gc\210\207\224\342\261?\371\037b\005X\274\270?.\224K\253\014:\307\277\326]\354\314Rb\177\277T\310@.\232-\267?\r\363\253ejn:\277r\366&\212\240\177\275\277\377r\354K\314\220\275?9\366\361u\222Z\245?\273\351\203\241\326\233\300\277\326b`U\366\336\247?v\337M\024hP\221?\306+\216(\330C\271\277z|\234A\325\347\242?\323\3622vy\345\222\277)M\030\203\"\267\242?\262\300\250\004\204\371\266\2777\213qT\261}\273?\241\263\326\375\"2\245\277\377S\301:\3010\323\277\367\005\352M\031\335\304?\324\363nZZ\305\226?\350\252Z\237v\350\311?N\254\035\3507\345\323?\356\005il\336\026\303\277\262\372\353t\261Y\274?\213\'e^F\277\254?\277\016y\004>E\243?\277\223\334\307\210Y$?d\351\303\'y\177\254?\257\240\365\270\257\261\306\277\307\260\351\214\275\206\264\277\213\256\017\354\205+\265?Pn\025 \032\360\321\277li\350\016\'\353\271\277\267\217<J4\025\274?y\335?\006Ay\226?>\266q\373\237U\246?\\\235\027\006:\033\226\2776<C\\\020_\246?(@\017\206\213\240\274?Br\240\377\003/\271\277?(L@)[\262?\322>W\342\';\313?\\\0177\230\366\341\303\277\242P,\025\0035\213?n\234ob\305\350\225\277\261\301s\310\002\225\265\277{\270\374A\014ip\277\377o\251j>%\242?\036\031t\214(\371\312?\2455Qu\2026\315\277\021\317{E\234\243\217?~-\237\036;\232\244\277\255\243\203\266\353k\264?\254\022+\306\356\201\231?e\033\026i\031\031\221\277\006\206\021\345\305\304\244\277u\036\313\206;r\256\277q\"\377\335\t-\243?\031\214\210\256\221\225\265?\372\276\240\354w\225\272?=\205Z\326\242n\241?)\326c9\347\032S\277k\337\347\223\357\004\312?\037\230\334\344\223\r?\277\244\013\300\204\204\000\260?\t\272\242\326Kv\315\277\311\373\272\323\360e\230?\005\317\210N\232S\276?X\023n\242\222l\256?\237\267]\221\243\311\304?\004\275\027\276\276\221\273\277\343\276\377\361\027E\257?]f\231\376\353;\264\2776\221\233\r\'\335\207\277\235\032x\336\221\320\241?\23071\252US\260\277\177\312\031\277\327\333\303\277\2777\222\2125\032\301?.\316\227)\336q\232?\367\206p\014\"P\223\277\177\"\344\256\017}\220?\307\271\333\275ka\252?\274\222\235I6c\266\277\234Y\007\372\252H\255?M\0224w)e\253?\201\256\n\200\"\222\255?@%\333\\l;\272\277\377zp\206w\353\320\277#\361M\316\217\321\216\277!\017+\032\355\257\306? \005\001\3272\225\275?\2114\346<\263\216\267\277\276\037z\331JB\250\277\002\217\263:\204\305\256?\014\003Z0)\251\235?\270oc`\007\347\274\277\332\322\273\027\252O\270?\210I\366\263\2212n\277Q\330\261\310\331\244\303\277p\030\332]\035\235`?\200\231\306\355\302\210\245\277!\202;\032Q\252\315?o\226\315\242\202f\261?\2657\224m\'\222\270\277\210S\230.\300\253\262?R\375\245=l\033\317\277yltB2h\252\277\226\364\025\200~\000\274?aJ\\\3371L\270?\334\210y\225m\256\226?\274\004\200V)\010\247?_)`\220\354\031\251?=\240\316\334\235\027\260?\360\372\3478\024\224\265\277\221X6Y\334\343\302\277\000\237\242\021ih\243?\245\343\013\202\210\322\232?\350\342X\'\230\276\271?\310\235\002dy\231\212?\235\034R\215Ud\321\277\257%\3442/\203y\277R\025V_\023z\277?\233\2215Y_\360\276\277\326\014\336;\244\216\237?2#\200\035z\370\270\277\275\034*\204\220O\231?}\207\327\371\n\034\306\277\375\241\245-&\254\241?4\215Rh8)\263?x\260\251\th\\\303?\006\267=XZ\007\240\277-\313D\273\310\373\303\277cG\224\303@\263\254\277\363(\373\351\014\314\237?[\321AwnW\260?\307\267\227v\037?\247?<\367E\n\353b\304?H\317Gn\030>\272?\r\214\357\0311\242\303?H\005.\341\032\003\302\277\232\304\355\220B\005\276?h\257\nhs@\250?\213\202_\312K\'\230?d\2202O\200\202w?\344\357\254E\206\254\263\277c\221?\353\rp\233\2779_B\351I$\310\277\005\251\276\371\231c\272?&\226n\327\250\265\240\2772\005\203\001\366\320\304?\300M\030q\3174\277?\357\237\372\231\232(\237?\340\0362\364\342\003\226\277i;\327\264\215\334\272?\263|\276\234\370\211\320?T\3042f\210\371\305?\330\233b\\W\253\232\277\n\342\247\343\374F|?6\274G@\250\373\245\277l?\030\305#\202\305\277\352@\242\241\2407\270?N\314\355A\237\214\212\277\370p\347HH\377\262?\373\242\330\0368,\266?7y\003\303+\206\264?\360A\233\354#,\303?\201VM\007\264\334\275?\200\017\370Q\337\211\265?\251\363\005\3708V\303?\205\002\004\204\244\374\234?\245\312\316nQ@\265?)\004\303\227\033 \322\277\211\241\342A\377L\266\277s\356\315Y\372\245\267?\2433\027\207\371,\201\277\273\311!{\212\"\316\277\267g\032\315(`\234\277\303\313\273\177b:~?kW8\324\237\267|\277/H.\321Fx\252?\003\304W\225\007\335P?\3163\026F\315\333\270\277\355\245\252\2662\003\302\277.r~\000\254\017\261\277;\371\314s#\010\271\277\362\275\204\342T\307\333\277\'\247\366\224o{\301\277q\002 \255($\267?\265\265P?*\'\260\277\377\302jS\032\n\322?\251\223R\2659\232\226\2770\307\341\027\002\347y\277\320\tgy\\\311\220\277;7\212-\215I\274\277e\315\305P\353\336\230\277F\304\365z\277\362\262\277U\252\252\233\203\336\252\277\025F,\014\274\177\264?\312Rd1\251\236\242\277\r\215r\333\350\313\321?9\231\366\263\030\306\262\277\325(2\356\013\227\206\277\266J\340wP\023\262\2778pC\0245R\206?\254\362\241\371\263\354\253?\230UB\232\311\347\267\277I\024\337\300\021x\273\277X]0c?\271\312\277=\357\234\334\221\020\213?\337w&\301\324\006\261\2779\242\026\355]\000\203\277\252TC\034\014\305\256\277\272[\344\352\273\340\300\277\200\332\024\232\332\031\234\277\007\266\272\374\244\242\253?\360\026\020\213\371J\242\277\023\212\007%\\\326\331\277^\322\355\275\3056\260?\273\325{)\361u\242?F\351<,\025\021\265?x\305K\246\326\213\262\277\225\333\272\277lA\216?\225F\215\222\340\244\255\277k3\275\034\037\351\245?\233\320\031d\205\246\217?\272\234Y@\345\302\246?\274j\200\033,\315\260\277l1\202[Sg\307?\240\207\320fHf\255?\242\256IW\307\357\260?%\306\325\177\023\275\305? \355_W\245\335\277\277O\025\334\'\226\242\263\277D;\226\244M\003\270?\237\333\377\010L\325\271?\217\010\265\225\351\212\244\277\327\353k\2209\346\275\277,&e<\266^\211\277\263\000\341\353d\231\263\277A\322\373\375\026l\256?\242}\250t\345\265\235?\274\023\021\340_\360\270?oQ\315\346w\247\264\277=\231-\n\267?\272?7\323\305\217\022\177\254?\177\333\312{\302\277\311\277\275\226\227\304\362\252\240\277\370U\356_\363E\250\277\335\036G0\2465\207?k\354\351\tam\254\277u\220v\257\313\250\217\277\017\0016k,\264\213\277\220F\257\304ft\325?5\254\014\204\302z\261?\352|\273\341\224y\305?\306\225c\3656\027\304?\364M\324\222<\357\263\277\025H\366~\020O\314\277i\367\257n\275%\325\277O\256\204Md\363\307?(\0231\256;U\266?\302S\354`\3778\301\277,\237\271\217\361\342\261\277\323\000L\216W\005\303?\230\034G\252\346\235\235?\247M\223HI`d?\350\301\024\363#y\327?D\223Cj!\004\262\277\031\306\265\013\210\261\252?\202\330|\341\221\260\256?\352T\245bEo\254?}.Ihx\377\205?G\331\304\272)I\322?\023/I\327(\003\256\277z\024\352;|16?\2357([\310\324\315\277[\300\346/\2630\257\277\231\366\'d\305\002\310\277\211\246\'i\337RR?v+n/\316\243X\277\031\213\375s\261\357\272?\204\256p\373[)\242\277Q_\241\332\245c\267\277\347\257\321p\350\247\300\277\255\267\271\300HLa??pK\024sI\226\277\251\354\346\274\237\366\311?\230\223s\332\340\351\210\277; \036\021\352\021\242?j\215;4\377\326\261?k\030\035\036\302*\245\277\321\373t\305\266)\215?,2\033_\222\005\271?L\236DB~\257\216\277\341\237\234\342N\337\273?\216\314a\214\213\\\235?z~\347\201\020\315\260\277\022cS\323\342\273\311?\177\374\361\025\304\340\215?\324o\0270.3\257\277\370\252\360\250\227\013\276\277\367\220S\316\021t\302?yG\022@s!\236?\374\375\244\3125\'\300\277\215\036\305\375M\362\266\277\321? \033\320\327\212\2770\231\251v\031i\261\277\355I\356\223\262q\252?_\2361\246\216\200\251\277S\363G\357\'\005\246?\267#\204$\211\333\224\277\347p\033wA\337\304\277H\026\230Q\217\017\242\277\n\312i^\375j\304?M.A\344>\006\242?\327\270\265\364\325\023\321\277\342A\341\243N\200\232\277\034\001!y\002q\247?V\244\253\305\003\340\321\277yAM\0273[\273?d\204\236fw\304\300\2774\242u\261\306\237\242?\013Kg\304{\360\251?l\251\334\024\323U\226?\236Xt+h\233\276\277\306:\322\014`\340\262\2774\220.b\272\000\264\277\217\312Z\276\257L\262?\207\220S\020\320g\312?\262\230\036\237\010H\245?\006M\027`\377\352\271\277[\2450t3\026\263\277\247\374[+\351\251\310\277N\350\2279\003\343\261?\341\345\331\353\024k\254\277\222`U\024\370\025\243?tF*\2267\360\276\277W>\363\256\027\240\266\277\220=Rg\316,\260\277\276`xmj\335\223?P\250N\247\2468\202?L\263i\240\216\345\270?\346e\242\275ba\256\277[\210JY/\005\270\277\'\262d\351\030T\216?r\026D\025u\341\265\277\201\256\371Cx\250\226\277\220\216\0039\023k\250\277f\262\261\227.\211\225\277F\270\300\371\325q\263\277\203\206\277B\201\n\304\277\021\005P>aT\271?)\355P\346\370\246\207\277V\020\356!\363\225\243?\003\225\337m\262xY\277\216w3T\020\350\227?\267\237\354\t@\254\231?\350\031f\240if\277?Al~\276\347h\250?\315M\013L\031\'\315\277\305\t\222\212\2220\274\277w\331E\336\336\254\241?z\203\330\204\000\202\265\277w\017\272\303V\241\272\277m\226D\346\3727Y?,\3566\354&\010\240\277\010\216\337\252\245\\\252?G\3038\216\224\364\224?\367\221#\307\307\275\274\277\234\251\212\366~\240\267?\250\364\251Hi\213\263?\030\221\375\013\374\234\310?]\213#\314>o\257?&,\232\222\2617\225?\273huH(nk\277\335\202\337(\3040\264\277\314\367e\264\\ \301?!\025\321\016\331U\242\277\201\025\335\0215K\304?i\371\001\256u;\260\277,\201`\250x\016\250?\316D\224--\346\216\277^\336\r%MS\300\277H\3479\366\265\301\250?\265\317w\274\3059\267\277WhGr\372\255\244?\372\217U\224\370\361\276?B\310I\\\210\233\215\277\201\332\2748--\263\277y\006\362\227\216\230\234\277@:\005`\252\355\246?B\t\0238Wa\264?\334\021]\273\241\243\261?f\212\264O\311u\234?\237\243\203M;\361\262\277\224d\273iz\351\242?K\374%\365v\334\220\277\372\037\345U\222G\276\277\376\350d\024\177y\250\277O+\224J[\361\265\277\370G|\275\350\352\227?\220\335y\010\363\321\301\277g}\033\006bt\242\277E\016\032\243\357\030\300?\312\037\235\301U\034\257\277?\324\033\325z\253\260?\251\375\302M\212\317\312\277\220\340jR\241\314\241?\364)tZ\t\234\302?eI\270\373\204\316\305?=\202\030\263\016L\267?\373\2757\025\204\345\260?\202\346\330\376j\005\320\277?\377\023\007/\245r\277\332x\307s|\264q\277\266\251\364\254\204\242\257?k\036K\326\361D\247?\"0L \310\346\277\277e\257\020b\367vn?\231\375v?\026T\205\277g\226\203HL\306\254?\266\371.?\027\202l\277*;;\020\311\350\212\277\230:\230\331_\030\311?\356\271\032[\0103\240?\r*B\337\354a\267\277\243\2142N-f\257\277{\3711\312\206\235\301?QI\367l\212\214\234\277/\023\006\256.#\300?s\305\271\306\234\233\241\277p\263w\363\336|\241\277\335\266O\330\306\003\236\277z\353\375G\016\214\232\277\200p\274\323l{\304?\017\317\262\241\364m\260?\002\257\252{\004\227\275?X\017\220)\320\033\302?\026QU|\214\005\261?\361\314\362\214\314\235y?0\237\263\300\024\"\234?\327\260L+/\035\323?\247\001\206\364hZ\274\2771M\213\320\306\372\261?\342\312\024V\032v\304\277\340\226\r\246d\357\202?\367U\313(\300l\230\277\263\330\332\206\345\377\243?\344W-F\025\246\221\277\200\036\241g\234f\244\277\307$\026w{\030\264?<\250jW\3616\231\277\250\3414\315\"\276\272\277\256\336R\373\317\367\305?\304k@U\2543\270?\316\236W\001\372W\212\277\246\026\005\022\2341\215?h\275F\034\307\255\316\277g\357q\312\021\252b\277OV1\274\320\276}?\034\260\375b\335:\221?\023\273*!2\266\270?\013\264\214\234\251Mg\277\373\016\354\340\217\321\217?\'\034\377e\027\027\267\277CX\021kU\013\322\277\230U\200\001\254\267\301?\337\337|\275\226\212\254\277\004\327$\265FS\311?\216\212\305?\305\222\251?D/\335\264g\033\233\277xK\371{\272\205\267\277\231\252O^\366p\240?\235\177\025\360\375\250\200\277h\261*\016]\231\245\277\207\356\255\206b\014\250?\004:\363\266\351x\321?\243\340D\341LQ\272?\025V\n\024h\325\262\277\243\'E\310\017]\303?2`_\014\236H\274\277\230tS\262\3504\243\277S*\002\202\253K\223?[.|\023\010\021\250\277\310\220\005\204\214\274\274\277%=\013Jz\013\241\277\260\217\353\356o\234\274?mR\256\233\nT\274\277\267\314\376h\214~\227\277\3306\363\300\345\234\225\277t\300P\273\305\017\325?\023\263\210\312\221\000\265\277rQ\334\027sy\217?)\r\354/\225M\267\277QG\367P\312=\306?\216\301\235\357\242p\265?\257\t\342\277\214\354\262\277),)\237<g\251?\232-}\017\256\233\244\277\303\306\"\363/\237\254\277\227~\312\200s \262\277\370\3119x\033\242\251\277\333\300k\242i\367\217?>\\2X8X\275\277\310\371^\270\241s\304?\353\327.\337\0209\251?\231\320<=\006o0?\034P\354M\217\376\301\277\371\270\301\230\032\320\267?\3531|\236\031n\230\277\003\227\020\363\202Z\300?\204\2751\321\032\243\320?\306\213$vNc\253?\3515\007Tm~s?\023G\212\031\216V\274\277b\003t\tu\022\320\277<\034q#\210{\260\277\253\313\303En\037\246\277\252\010-\265\024\'\277?\242\352\202\307\221+\254\277\212\350m\304U\263\266\277\262\324b MM\312?\304\374\301\310\323\214\226?\311~\2669\242\362\262\277\373\345\032\036\240\205\256\277\213s\311_\316\014\260?\016\312\311\037sz\255\277\263\205\316\211\222\212\301\277%\236\210\n\351/\266\277\347A\235\3416\243n\277\202\332&\214St\274?j\031K\372z<\240\277<\304[\231\262z\255\277\265\003v~\243\356\201\277`.)\000\273\244\221\277\\\350\366\227Y\212q?$?\323\301\322\236\244\277/s\016t\346\037\276?D\304{`;\250\257?\375\2467\346\212\000\272\277\230p\376j\036bv\277nD\314\231\271\256\306\277\310#\036`\"$\217?[sD\273\230.u\277\211\034\332d\233a\252\277\246\207\027I\254f\302?\376\260C\212\255-\321\277M\246P\215\017\370\214?wE\320\217\364\303\263\277u\247\357\310\203\332\246\277\235\252[\340\305\030u?\202\247z\373\256\203\266\277\325\026\335P\266.\265\277|\264\334r\240ow?J\351\346\343\370\201\237?\nKPj/[w?\344\311\263\322\323\303\270?-\031fA3\300\252?\241w\356\021\365w\222?_\"\014\240\237\\\257?b\241\323\207d\007\265\277\267\"=\224\361\316\303?\r\007\233i\233=\254?\356\267\013\372/\373\310?\214\320\024C\343\365\224?\227I\377\373\002\265\261\277vU\rr#\226\303\277\372\341O\376\344!\257?(\250\314\332\305\356\240?\230\001\207\214i\323\267\277;\345u6#\216\270?J*\235\0165\004\252\277x\355\344,#\016\305\2778\325\200t\321\203\230\277\217\\\026\365\377\305\274\277\214\253U\372m\253\270\277_\223\223F\032\326\224?\257C\273LQ\201\221?W8\244j4\324\263?$\344v\324h\326\216?\221\200O\223\204\235\243\277\375\357~2\247e\304\277\3239B\350\031\354\277\277\376\261f\032\000}\263?Ss\367\307Pz\274\277\230\274<_\223d\256?2\232\273\341\030:\260?w\241L\035H\256\265\277\206q\247\235\014\256\310\277\3545\305\211\334#\255\277\237\216<~t:\260\277+X\231\305\213\024\313\277\"\321\313\032K\364\224\277\014\020\240>\3055\301?*%\271\000\213E\214\277Wj\357\264\014\020\314?F\200>M\210\375\254?\246X\020\333\3707\227\277\304\253\202\212Uw\267\277\025Mn<e/\265?X\265XN\377H\205?c\3258\260\235n\261?\030\023J\212u@\276\277\rCc\337g8\272\277\243F\334\246\323\271\243\277\242\037\314\335\201\030y\277+0\353}\n\373\275?\205(\221eK|\261\277\250/\213\016\004\260\217?3lc\302\207\375\250\277?\255|\237\330\300\222\277K\374\276\026\022oj?~\r\202\274\t6\302\277\005\343\030\n\014O\244?\343?\373\253\342\376\252?i\037\365\245zQ\243?\362A\302\211\214\377\246\277\325\246g~\332\373\231\277\267C\347%\307D\302?\311\341Q\307\215(\315?\257\'\341\271\326\030\251?v\366Z\177WS}?\\f\316\244\237\361\266\277\034-EB\344\335\246\277\236\002\302\223Y\311\211\277\347\341\361R/\274\266?\225?\230\366/\377\235\277\031N\002\207\256}\215\277\375\t\271\256\034\365\254?\320\205kzs\036\246\277\265\276\r\263\373\267\245?\n>\020\237z!\260\277\261\351\300\212MB\276\277\2205d\333\213Y\251\277qC\310\205\003\r\204?%\322\355O\234\300\301?\007\276\311\251\010\201\323\277\254yQ@dj\221?~\027=\200\355\207\256\277VH:\345@\331\237?\010\317\006\204U&\205?\274\203zJ\233\201\255\277W\303%\342=q\262?\355o\222\276\t;\262?\2205+\3600\037\273\277\347\225\344\037B#\201\277\235\264\231\003F\336\263?\000AD6D\353\317\277=y\223\215\340\nd\277\037~nhb}\245\277\013\277\210\023\357\235\245\277\272\001\235I\331\330\272?\224]\003\315\312Y\223\2774!\231\2453\375\265\277xi\202\202}\322\303?\314bDLA\264\240?=\310AK\233\241\250?\266\017\026M\005!\252\277\277\210\014\220\355`\270?:\357\320%2\336\262?R\372\302[D\347\263\277\341\022\257uO\226\264?.\246v\321P%\234\2772\222U\343\314\324\223?+\262_{\030\007\252\277\341\014^W\345\326\303\277\347f\225\007xl\260\277~\305\355\266s\206\316\2774\204\340\331i)\233\277(m\335\264\006=t\277\235#D\026\270\356\263?wj\013$%\322\232?z\200\240\255SF\271?E/\317\210\213|\215?\266(u5K\363\261\277,\003Tb\2237\262?\230\r\030\304Hw\261\277\311fj\034\362\212\242?\302K\354\305-KV?\'\2063\322\367\026\240?\232\0137V0\022\214\277\230z\250\230\213\213\262\277\265\211\302\253#p\312\277U\365\027\262\241u\275\277\336\343Lb\302\205p?\031\')us\010\261?\260s\301I\340)\300?\\\250\032\310U~\223\277\317\304T\300Z\316\260?r\303\342\236\3217H?\203s\005\300\235\005\260\2773c+):\007\242\277\005\314\362\302w9\222?C\350\202\324\262\025\201?\016\235$\324\244\272\261\277\322}\014J?\314\302?`\321\203\203\t\204\310?2\244\243\370\324+\250\277\347\342\320a\364\341\221\277\230\023\305\033\321\337\264?$Y\263g~\363\314?\265\251\010f\231`\251?\200M\234\267h\033\250?\260{V1(\346\302? _g\024\005\216\254\277\272B\373o\364\274\251\277\217\016\n\033k\376\300\277\200+d\027\024\260\262?\354~\261\267\340\361=\277c\036\023yA\200\236\2770\244{\202\250\r\273\277\347.\014\r{\232\253\277\013\204\313m\301\250\311?\004\366\342k\311\005\200?\313xY.\346n\263\277hp\277\035p\000\262?\335\201\367\210\211\312\250?\342\370i\243\331T\256?\356\234\t\023\360\246\255?9\220\315tM.\231\277jpNA\372k\306?\025\331\206\347kW\272\277U\314\337\250\225 \261\277w\305m\213h\201\260?\374\312\307\2049\242w?|\357\310\252\177\373\272\277\263q\010\245\266\236\244?\214\252\234\000\223\322u?m\274\036\216\354\376~\277 \324\270%\037\340\257\277\265\363\245T\357\004\257\277V\375\345\223I\305\266?9X\324\372\306Y\277\277M\322gpya\237?\313\272t\365\366J\322\277\027j\366T\224\250\202\277\354U\255\307\217\r\235\277\316\232\305\026\nw\303?\331\026\204Q\241\031\251?TR(5\354\353\277?\210b\354\320\266J\256\277c\334\332\377\340\231\266?t/\033\267\023R\275\277\021\013!\314\221W\275?\345\254\311\212\024\013\260?\304\362\363\220B%\265\277P`\322r\263\242\277? yX\373xn\314\277S\177Q\325>;\310?\253^y\006\203\263\300\277\342GX\032|\306\214?/9\026)5\001\266\277Jc\210\203-\303\220\277[\3137\207K7\274?H\207L\306\005\024\226\277\263\335\211\200\354\203\214?\273!=\366\346r\257\277\202y\207\355\347^\250\277\246\227\\\362A\023\300\277EX\036\036<\371\251\277\303\356\306\321\010\215]\277\221\365\331\342+\262\262?H\220\016t@f\220\277,%\316\346\017\345\260?\027\217\252J\330\271\264\277\221\350a\300\"-\276?3\226\347B:\035\321\277U\2540y[\345\252\277&[\226}w\304\271\277\266$\275\225\225\001k?\300\n\364\327\335\304\306?an\030\'\351f\260?le\020\324\2000\222?\232e\024j\307\263\302\277g~\322\326\240J\264\277e\t\205-S\311\250\277FG\227\353ch\272?\224\265_y(R\306?.=\236\220\213\217\177?\231\314\376\273\377N\221\277\261\275\220\273\3247\262?\351\216p\0211\035\263?\r\271.K\020\232\237?x.\221\2332k\266?\367\335g\252\362\330\224?c\262{\006VV\254\277\247\177\261C[q\267\277\261O\000\241S\304\246?\033aQ\334\334\006\270?\"0]Npno?\323\376\304\264\336\022\242?\367>.\177\203z\305\277\237r\214\276\2416\243?\360,{\340\371\351\231\277\211%\005t\017\024\305\277\251\004L\375\326!\233?\275\".\231W\030\262?c\270=\355\256F\253?!\241d\335\217\231\241\277u]d\261\300\237\267\277\261@`\270\027\007\247\277\360S6\233z\243\217?\ni\215o\232\215\254\277\006s{\n\3034\272?\254\315\231\010N\"\276\277\375\\>t\033\316\311?F\260\3313\355+\221\277\306\352\026\357\251\234\260\277Ej\263\242@\204\245\277\273\210\332#\203\010\222?U;\362\002\327\t\271\277\026\343\036\225T\237\263?\315x\237\232\201\341\311\277\372\327\254\370\344\026\256?\2749\341\231\323\264\257?\007\256\321)!\027\262?\022\265\331\343\032\033\274\277\025\355%]\346\374\261\277s\030\303\243\304\014\301?\017\002\270\346_\211\271\277\227D\306\263\312j\264\277,4G\264u\247\256\277H\230\252\334=B\253?\314\345\001\024\023i\230?x\360\374\323/p\212?\003W,]\313\270\270\277w\024!e\3327\262?v)\217\323\326\352\302\277%4Vo\220\342\245\277U\323\222F\211\254\317?\361W\344\214\324K\232?y\224\216^c\351\270?,c\013\271\3127\271\277\366\263\034\356bv\275\277\000L\272\t\277\315\227\277\353\302\\\'8\226\271?1\342\004\r1b\323\277 \226\270<\"\231\217?Y Q\007\375\235\222\277\326\\\014\'\033\202\272?\341f\374\236\275\202j\2776\246%\214\355<J\277\354\001\266 \371\334\276\2772\3529\306\225~\306?\315\004;\007\005I{\277\262\225\326\276\225\005\273\277\312\033\301\327\007\307\260?\000\325\026C\252\014\277\277SC\3420\324\233\243\277\323\304Z&\373\357\303?\274~E\307\364-y\277\'\276\341\306\3647\320\277\325\315\260\224{\260\233\277\310\0144\271Q#\213\277\270\177\352%\002\266\242\277\370\206g\204lI\250\277\027\024\272X\334h\251\277]\267`\037(\233\321\277\300O1\000%\037\260\277\327l\204\320F\357\260?(\001?\261t\266\322?\272\027F\331\372\234S\277\274\325\374G\333f\271\277\227(\350/\347\303\262\277C\307L@$\336\302\277Y\300x\307,\355\301?\364e\010\377)\033.?\026\373\360\271\215\323j?\225\375\327\301`|\320?\253\362\377\221\367E\251?\201r{\344\232\225\300?\227@\357\243s\014\322\277E\344\365\306\376S\303?D\224\360\202YJ\261\277\256\363\265\0322\257\257\277\344\347\235\2553\306\233\277\215\311\2412\206h\212\277\243O\244\275\355h\244?\221\260\367I\220\363\313?\276\030\252\246U\241d?\271\224\032\377\334r\203\277+\214\332\366\200b\273?\032\000\240\211a\256Y?\010\303\246*\224E\253\277L\207\256Y\225\231\220?-\017\371?\361\013z\277>Go\300\003\377\262\277\245\016#\253\037R\303\277\025\300\377\210\237\222\276?N\305\321\r&\361\212?$\267\246K\255\035\242?9\017\323\256\265\243\272?\232\3452\021\361\014\260\277A\304f\227\213\367w\277q\247\014\356\031\204\300?~\324\307\351\215&z?\025\224r\230\341\307\321\277\310\0329s\231\273\244\277Ox\357\216\030\267\237?F\342\244\345T\271\267?oN\3519\223\\\227\277\000\003%\036\0065\273?\342_=YaN\300\277\214l-\210\016\214\320?\032OE\260\320\222\272\277\216\322\210\241\371q\302?\007$\207n\354<\307?\211\017\262\rDa\263\277\016\253\037\230;\307\212\277U\366C\n\273\332\246?A\224_\364\274\360\233?\240\254A\313\261\360\256\277\323\233T\357\004\216\251?\251~\233\241\377\205\310?z\323\221\3223\335\263?\266K\245\014\367\033\270\277\301zr\265ki\265?\177\210\220\214\371\376\207\277\307\031_l\253\200\266?\335\'t\037\032\374\247\277\311\350\375?\001\275\243\277!:\234\252\207\300\241?\020\255\370\266?\206\244?\222b\267u.\022\271?\016\376<_H\231\250?4C\242\035\363T\262\277V\351\232Z\211\364\244\277:\254Y2\322\201\243?:\374\026\250*w\272?\236X<@\000a\270\277\310VH \356\014\302?<\346\353\177\206/\312\277\"\033\373?^\263\221\277\200\360\311\262\240p\304?\335\0071\257\227\216\261?-\260\314\271S\253\324\277\214\372\006\313\217C\232\277\030\326\232\3520\305\240?7\274\213\225\306A\257?fRis\201,\242?\306\030\356\2501\304\263\277`\342\027\024\314\307\322\277l`\2420\261\030\271??<\341\300\203\261R?\306\332l\034\324\244\272\277\310@\364C\034\266\274?\316\336\305\363+\001\261\277\276\016At\036$\246?\026c(\025\n\373\260\2770\322<C\222\352\234?\014\021\235-k\343\254?\220\017\010\340\272z\274\277\365\324\323\252\255\021\263\277a\'m\310<o\235\277\327\027\267\315\236%\210?\236\306\267\266\214&\302?\005\274\344\361f\212\247\277^H\000\315^\305\264?$\003\237\030\2769\237\277\037\024\003lgi\276\277q\255\354\235\016*\247?\250\205Cm\333\274\225\277\247\202t\275,Q\247\277\357@\020\247\220<\245\277\377hT\020l\307\211?%\006\222|\025d\230?\334\224\343g\016*\310\277\273\223<Q\262t\274\277\255\333\211I\374\350\226?!\231\003!\327w\306\277\227\231\355\225\274\216\270\277\270\014v\336\324\031\224?0\321\2524v\206m?h_H\314\232\217\310?\356\216\210pa\260\201\277;\212\177\262\264\242\262\277\272\265\251\277O\304\302?\242\2204\316Fd\302?\200\255\244\261}q\243\277=v\256\377\261*\206?+\342`\177\010v\331\277g\203<\347y6\253\277\343!\024|\236\330\216?\317\256\341-\374\013\201\277\021\\\350I\332v\247?\000\215Xe|\026\261?\241C\230u\006\006\227\277t\236a\005\r\223\313?\347f2\231\352\234\264\277\350\226\261\3157\325\264?\256\023\355\031\2423\253\277\216\275\203G\232\210\260\277\225<\341\335\2712\270\277\361\265a\303H\353\275?(i\350\010L\362\301\277\273k\324q\253\335\263\277WB^\305\016\200\246\277\222?\030\322=\216\260?\213$\323\r\342\026\236?\250R_\271\254.o?\251\304\230W=(\312\2772\022\273\346\335\204\300?.\006N\032\'\232\271\277\274R\360\306\031@\270?\357\371J\204%\255\030\277uNL\004\367\322\260?\263\307\316\276\007s\200\277\301\326X\374}#\235\277._\266%\034\334\316\277#7\356\261\351_p\277\0239\177\233\314\311\272\277\306\336?a\340n\254\277\304A\203lB\265\276\277\t\266\004\214G\301\276?zRs\233A\'\322?\374\367IB\265\344Q?\261\374+\367E\262\206?\255F\217\312D\210\215?~\235\002\353\031C\222?<\270\334A/k\244?Gy\201\203\036\377\245\277L)[<\323\302U\277G>Y\336!%\310\277\264\222\327\0363\r\266\277*\257\024c\363\351\320?\224N\332>\362\243\267\2774\243\'\210I\225\263\277\315P\032\211\313\310{\277\247\202\235\031\364\327\242\277\224\315j\274\3628\222\277\340Q \014\206\327\215\277\'=\3638\325\272\320?\272\010t\\\206\"\311?\371\356\231\3072\"\223\277\001\320\205N}O\255?\000\017\376\025h\327\265\277G\265\t\266\2216h?\343\312\260\372\245\304\272?\356#\014\002\264\006\250?\255M@Y\243\206\322\277\253#\324 \303m\260?m\024\005\375\033\200\241\277\350+\201\277H\207\266?(\243\314\346v\356\276?\333\2141R4)\267?\336\347\353\220\266v\301?gC[\222\224d\273?Q\304\241\210\n|\264?\243\316L~\271\034\302\277\264\212\304\253\274\256\242?F\311F\177XY\305?\204\307\262\\\205S\233\277J\243\001+H\326\261\277\244s\340+\367\370\272\277q\0133\322\006\314\260?\334\263t\364\317\324\301?\022\265\214dp\316\225\277\364z\336\325\244\303\252?\'\030\315\272n\"\260?Z\276\236\000\365_\266?\3732L\352A\214\317\277,\235M3\301h\265\277$\030\264Q\336~\250\277\347W\277-\213\242\237\277\033\312\3502\235\023\320?F\23147\347\025\251?\272u\311\376\216\256\214?\342/\374\207\254Q\300\277\220\274\307\217\326\327\222?\310\274A\346\2259\242?\357#+\032\202\006\245?:\023l\2721\333\255?#\005B\247=-\247\277~W\177\350\253\263\220?\260\231\223\217\230<\255\277\037\213\264\312>\336\304?Mn\376\213J9\263?\367{~M51\302?\353\247D\243\235\306\326\277\252\363\225\251\320\n\237\277\373\024\245t\372\204\254?\333\376\2555\366Xs\277\233\267\200+\217+\210?\265\263(/L\027\240?\213q\236\221\014S\242?\233\000\200\016\\\220\243\277\271(\004}$\026s\277\230Y\235/\356\014\243?\021m\030\307\213\340\275\277=\037\215\307\357\201\216?\036\272\207\327\344\036\265\277\244\272\275\315\007\206\220?\3449f\264&\256\315\277\360\2315\316\361\007\272?\371\375\366\211Q\177s?n\007\271ib\005\272?\225\323\006H{k\205\277# \017[\240\341\251\277\202\"b$\311\020\263?\302$\352>\343V\307\277\362\027\376\n\033)\242?\351\212q\225)Z\271\277ct.\006k;\303\277E\345\030\254F\020\272?\312E\014\323\263f\213\277\006G\333\030\274\320\260\277\341\346\025\013GS\245?[W\247\013?v\273\277;\016\217\010\227I\273\277\016UCx,\262\310\277\024\222\330\351\313:\307?@ju\227\322T\271?\022K\013\0348\371\300\277\262\0045\326\203\311\220\277jb\251\342R\353\241\277\353\243\262\250\374]\266?\255|\210g=9\230\277**2\313\234\317\300\277\311\312\205\222\206v\272?A\\\361\024\003\005\217?\271y\260[\211\266\255?\212EVd5\322\305?\000\257{\370W.\251?nB\255\304\221\265\266?@`Q\202g\260w?T\246\357\010\304\034p\277\016\342\202;\275\377\301\277\31517\273L\200\251?I\274yN\3727}?\252\272\333\'\342\215\242\277\254X\234\312ly\270\277\201\300ZL\354\320\273\277*\355,w@Ai?`\202\371\033J\323\243\277cs>\355\253\311\310\277\010\263}sBm\266\277\272N\r\335/2\307?\200\251\343!\274\311\312?\313\215w#|K\231\277\342\213aV4\326\260\277jBEYj\005\302\277\032Pb1\201\226\304?\247M7\000\014W\210\277\034\305\376qc\317p\277S\312\360\031\010\370\300\277\262\206>#~\277\276?\370{\264\205\335\261\263\277\347\306G\r\202C\250?\222A\354\346\317\006\306\277\276\216$\253?.\210?1\242\367^\257Z\305\277\372\016\313\027M\221\272\277\325h\331\266\2762\220?\024e\354\315\267E\271?\327\370&3\2153\277\277\257%\314\330g\007\307\277\027\350\371\276 n\230\277\273\340\277\240A4\315\277j\240\361\233\234\264\300?\360*x\204\361:\270\277\274\303ms\361\306|\277\370\222\324\037\310\255\321?`T\017\244\225\270\256?\321s\301\337.\222\262?\026<[\301\243\276\261\277\306\037\326\272\3452\303\277\205\267bD\203~\225?\013\224T\214g_\245?\023\013\246\326Mo\302?\316k\223\324\032>\251?C\333\346\351Q\025\264\277\257K\n\203\\A\303\277c,\345\363\326I\267?1\017\200a\246\346v?O\260\236A\357P\241?5\243\321+(\255\302?}\246\235%\002\327\211\277\314\373\321z\207\206\256?!\033nt\242\326\242?\226\005\307\272V&\276?\220[\261|\247\375\263\277\352\234N:\'\036\307\277~\221\230\346l\r\322?\324\200%`C\341\320\277\200~\263\322\256\250\272?\"]\215Cv.\220?\010\372\275\177\003\344\304?\342\200\244*,,\265?\356\001\305\220\257\223\256\277\273\322X\366t\331\316\277\360c\313\352\356_\263?\265\275{\032\304\334\272\277\346\027\036\353\320\033\301\277\323xC\3624\334\320?\234\004\026[,G\272\277R\214\224\032\010O\300?\337M`)\361\373\273\277{~\325\226A\014\305?\233\316\357PxV\263\277\266\335\n\211J&\321\277\256\226\3528\246?\303?}g\273]2\264\314?\372\376k}\2037K\277@\201\213\321\323\025S\277r\325Y\363\016\037\275?\025^Q-~\271\267\277\031V\214\372\227\000\312?\002\027\251\267\005\205\266?\022J\276\272M\'t\277\035f\016\004\203c\232?\355\225T_)D\317\277y%\2258;\231\264\2774\242\377\001[*\202\277&y\364\323\256K\253\277\213\215qm\331\345\301\277.\363\357\000\336\033\241?\361\3632\263\342{\301?\003\252ZG\275O\233\277\'xf}\3141\273\277\2221\234\331\002\010\302\277\312^\230\006\252a\261\277i\340\203:\320k\325?\301%\367\362\324\215\230?\256-\t2\267\334\237\277=e20\326y\307\277\365\016Aq\231\230\317\277E+s\367\370\254\271?\246\372\331~\204\325\266?\"\224\256I\343\333\306?[0\320\337\026@|\277\315\335\204bl\326\272?\210\241\216c\002\216\200?W\316\273\203\361\335\237\277L\347:\314h \273\277\321s+\030\326\n\275\277\256\240\\R\324\347\321?\330\345\277\204\267\364\300\277\n\262h\323!W\262?\222\257\223%\345R\254?\001C\010\250\260Z\265?\241y)\364\357P\262?\024\3027\251\343>\205\277\241^\363\217*\320r\277\273W\241\310\336P\264?\250\013m \260\243\271\277\342\266\200n\004\'\260?\3637\327\360[1\222?\334\213\211I4\035\234?7\247!\302R\006\310\277\004\222\004\352\003\345\301?\317\241f~\261%\263? \323\010\0265\031\311\277\350*\333Z\215\236\310?\304Q\0333\266\356\261\277\223\203@\003\3324\261?\254\344\356\000x\205\245\277\217\213\367\361\225\333\203?\275\021q\231$>\202\2772\226\233\r\336\346\271\277Q\007\0349\213t\242?E*\307\367\002\254Z?_C\253\377%\024s?\264\2320\276\276\265\257\277\001\346\377\307\243\032n\277~\347\016\350R*\270?\260\254\370\024u1\305\277\343:\n\213TQ\307\277N\002\n\335\321\037\260?\320]&/<t\252?P\355J_\034\337\257?\332|\240\004\211\277\337?\216\270\213K\240N]?\347\226\r\274\200\253\263\277O;\301\300\320N\223?+ \352\342W1\264\2778\350\330q\310>\227?qC\213\254\351\311\271\277\204\231K}\246\262\301?\204-$\333,\257\214\277\032\225\2161\377\212\305\277\r+\275\'x\315\321?N\201Y\223n\267\305?\211\337\037\016\326\027\265?\002\320\352l\344\261\263\277|H\324\304\224R\245\277\223??\267OU\312\277\n(8\035\026 \223\277-\213\"\275\031\027\251\277\262k.9\025\260\304?\206\250,\314\023\212\257\277\202\371Le\254\\\242?\245\266\373\351X\343\323\277\247\267F\320\325V\254?\200\372\332v\206\272\204?;\330\036\203\340\027\273?\375\022\356\254\0132\277?G\362\317Ls\255\213?H\376\373n\377\267\266?dC\024\265\3013\302\277\250U\255\025\327\372\206\277\013\014L\030n\231\247?\242\245rO\226&\206??\354;<\337;\303?\252\267\363\230\031%\242\2775E\3143\314\332\263\277\352\353\266&iB\270?\033\220,Z\300\\\222\277\213\340\3715\225K\303\277\200\216\356>\207\t\265\277\337\020\267\360\275\364\304?\347<AP\315p\272?\301\313\360L\005G\265\277[\204\t\352\302\240\252\277`%w\210yG\260?\335\342]\272\243\250\240?\244\005\023@\262\027\246?\375\346\273\324\'\220\300\277\304\336\247\020\257\262\266?\272\215\216\253\204\241\260?2]\314\213wg\267?X\367l\0302\332\272\277\220N\257\374\244\265\235\277\361\311_\244\273}v?h\330\002\305\205\327\276?\211F\363\322\345C\220?\327\220J\215w\353\260?\3023\347\"\242n\301?k\233\253}@\027\303?\335\255\353;\263;\276\277\007\336w\371i;\265?g\301\000\275b\220\300\277c=\355\366\354#\262?\031\337\225\220\231\322\266\277\361&)\353\354_\263\277Z0\0168\377\t\247?_\010s\214\221\246\300?\\\3668\0136?\253?\315\245\215\252\202\327\302\277\237)\036Z\014d\200\277\235\006\324\322\370\323\220\277\261\356\321\202\240\241\265\277\277E6\343\247\251\231\277\263\367}ky\354\275\277\216yJw#\305\217?\2479>p\3410\313\277\322f\233`\251\254{?\355y\320*\304Z\247?\264\000\333\342\324c\256?\'9-Erd\261?L\\\r\371?\244\266?\214\256K\271\037y\271\277\030\343\2014@\023|\277\202wPRMe\244?\010\343\373\355>\310\261\277\027\326N\031\301\222\234?\337>\3630\203\030\261\277RU\265\3431\247\266\277\333\246\025\346\354\310\273\277\266P\201\307\226\027\261?\232a\001\351\027\202\261?\314B\000\321\'\352\303\277\3262>\241k\267\271?Z\243A\2160\330\302?\"\354S=o\220\263?R\275Z\220D\352\273\277\016\330i\322\032\231\245?xu?\364/\032\200?$\240><\350|\234\277\006\016H\360=\307\301\277H\177\276\217\262\240\245\27791\305\'p\'\213?\322d\227\345eV\261?;\340:\255\332\313\230?\324=x\305\010\367\250\277\301`~\275\034\251\263\277P\335F\002X\376v?\336\360\353M\251\331\277?\3355q\263%\366\265\277\3471\307\031*\025\312?\351+>\300Si\264\277x\377\370\365\270\203\244\277e\345j\214.\023\275\277\221}\320\036o\345\262\277xs\000x\220\031\314?J\274{E\277+\266\277$\010\313\356\363p\277?\352\317\010\201\001\330\305?\303\"\303\311\035Z\262?Ts\220\347\350\013\257\277\031R\216\"\007\353\275\277\206=\313+\365\313\254?\2277\031\276\023\024\263?+\2445b\242\372\301\277\261\301\002\000\270\001\254\277\310\250}\241\261@\254\277\324{\214\213\025c_\277>o\261\3403\367\252\277\324\2226\230\341E\255?M\363q\033\211\353\243\277\034\233\362b \225\251\277\333\311\302\253\237\270\316?_\275\026=\336]\216\277\027\364\034\372\277\256\260?\256\035\235R\213Yn\277/\0175\363\266\204\216?\311\271\304OCQ\273\277!\033+\tU\311\237?\034|\032F\351\234\322\277^\364\373\204R\370\265?\364\026\212\033y\353\272\277\314\034\221\232Cu\202\277D\n\230\\\327\302\273?\343\neD`V\262\277\330\200\263Y4-\205?\250\366Fi\3360\307?\345\361\030\324\220S\300\277\366Tm\227\na\273\277,\323F\341*\204\320?\0012`[\2656\262?\274\242Wl\"\371\242\277\375f\017|{\374\300\277\\6\235\361\274y\252?\374X\3272\333\022\212?\322|rvV8\262\277\366/\324\377\235\022\273?\300\213\017}\244\036\303\277\203\371\342k~\251\261\2776\013\374\302\361\254\243?`\273\367\346\324\312\315\277K\325\353\347\364\020\244?]\224#\267\313Q\221\277\"\3676\240\255\377\324?Yx\255\261\347\205\276\277$.$\366\341\244\201\277\003P\343\327\212\377\243\277\357\376\275(*\240\311?1?\263\203\326\245\241?\237\222\351\261x\310\275\277\007\301]4\264\200\254\277-\375H8\372\026\302\277R\3341\215\003\222\254\277M\363k\t\365\327\273\277\217\002\213\231^\033\275?\232\305H\340\307\226\263?X\002.yF\235\235\277\017\0024\317\253\000\304\277\246_KZ\253\006Z\277o\276\346\205\277\232\300?\235\303\305ce\177\263\277\327T\342\344\263\006\255\277\313\031*\270$\214{?\237\200\202\023\037X\300?2@Pu\312%\272\277o\202pb\253\216\276\277U1R7\324=\274?\311\227\360>`\020\241?97\330\337RX\251?\310N\215\367\016q\244\277\265\271,\312~-\252\277o)\332\007\204T\264\277_6\302wd\336\264\277\376l\233J\347\367\265\277\205wS\030\211\331z\277c\217\214\225\225\235\335?\213_g\026\256\365\256?\275\034c\230\276\375\211\277\303\255\225\224$\354\265\277\230\370i1N\247\272\277x\327\330\0042\343\306\277y\247 6\257\370\246?\234\367\350\3111\320\240\277\307\357?\021\3012\234?\232\376\316\266\203\275\223?\247F\005\252\246T\317?q\\\013lN;\261\277I\022\370\364\030\035u\2770M0\252\350\311\273\277\020c(](\260\303?%D\365\325\014\221\217?aj-\355\2236\225\277\323l\265\224\023\373\265\277\t\206\225\261v\245r?{\0067\021\340*\300\277\215\304v\006\016j\216?\377\214\n\307\332\231\310\277\002\332\247\327\262\207\272?\311t\225=$U\301\277\337\016m\221H0\321\277SN\231+x.\301\277\223\265\206~dA\316?U\342\217~\201\250\333?\237\314\231\230i\364\321\277\251\374\327\313\271n\263\277\007\001H\200T\341\272\277!7\340>_\351\265\277\253\013\210\333\037\265\313\277~{;\210\232m\246?\022\356\213\013\001E\265\277\361\224\242\222\010b\307?E\204\265z\006K\247?\252F0o\377\362\255\277\223 \010 U#\207?Y\267,\037Gy\304\277\351t\275\315}\351\257?\035\235\224\376\300\311\252\277U\211jq\250\320\245?\351Y\221\332h\007\205?\277&\350\223\3479\307\277\327\210\001\350\273=d\277\351_\375\346\266r\241\277\177\314\302b2\005\263?\356s\351\007A\373\273\277\253\311\362\300\250A\222?\306\367\312Ie\216\260?\373\2220\316\355\211\222?\205SL\"\362\320\276?\\{u\215r\370\204?\225yr\306\026\343\254?\3232M\307\322\214\266\277\356\320\216\037\312\257h?|\324\272\254\210iw\277\032\355\344\203 Pf\277\356\374\302\252\022l\267\277\347\\\337\213\233\265\300\277\3713%\200\300\325\261?\037\014\330v\225`\301\277kR\342r^\027\261\277\347\236\204\340\227\225\254\277+\273\250\242\250\\\271\277}0M\245\370lS\277?t[\207\363\031\244?\201l\322\206k\037\231\277K}z_8\002s??]\022\303C\265\304?\344\357\326Nx\206\241?\323\006`\302j\214\302\277\350\367\247\344Eb\315\277\022\243><\266\350\234?\234\327\263\341\213-\271?\305r\216\221\220\305\250?\361\"\346U\220]\245?}H\344\2026\353\243?79\242\036-\017\313\277?\217\302\"r\231\275?\266\217\261#\332\213\256?j\320\305\224\345\225\314\2778b\240\304c\020x?\254\376\001\211o>\323?HQr+\372\335a?\021\370\227 \244N\303\277li\244\310\343\216\263\277\210\303\240\263s\257\251\277\307\217\334qr\205\244?Z\302\205\334\031\277\263?\345\242\263\270w\302w?\023\342\002\352\314\246\216\277\367\255\347\001_\257\214?\r0bd\372\023\216\277U\310\265\013\227V\251?.\334$\353\205K\266?0\204\263\253f\253\220\277\024#\270m6\214\310\277\316\316\266\224&1e?QZ\307B_\213\266?\034^\243\315o\322\206?\nln\227\364\026\233\277h-\374\274\203\364\273?\230f\357\244\326\213\314?\000 \017\006\314\301\245\277\177P\350Y\210f\250?@\361\202\274\214a\244?\334_m\327 \031\251\277\361\205 \314MHm?\007M\232`-s\301?\363\311?\365\233\364\307\277/\240\350\213\017A\327\277\335\311X\272.c\264\277\340\302,\203j\272\231? \003\360x\301\n\305\277\236\276\347\304a\r\271\277M\227\241M0\304\271?\206\232\327+\361\216\322\277 \320\224\036\354f\300\277\0263,\026\362\207\306\277=\215\303\365\301\325\243\277\024\374\376m\3510\275?T\317\226\373W}\310\277\016\'\007\013}|\266?]*C\266\344\336\255\277\312\233l\306\025\201\334\277\253G\344M\333\377\300\277@\022siD\331\304\277?QO7v\313\260\277\216\252\301\0003\320\260\277\030\202\323+[w\230?\357j\003\\c\014\252?\332\306\000\n\315\303\262?\306\000R\007F\365\265\277\201P\000\n\202\037\302\277\305\026\2325\2276\232\277\236U\230\2078\024\256?\227E6\204\327\271\201?\3772\352\227\354\226\250\277\201\321I4\242\210\320?\0323(\341\'\\\261?)\001\200\245\311\010\245?5rlm\303\231\310\277\357\373\367s\272\372\271\277Pl\3003\236\242\266\277\205F\030\025\225\023\221?\342\030\311\314\320\344\320?N\264\267\220\323\376\254?&n\3740t\241\263?\350\301{d`y\252?\225\177\030\344\244\017\263\277\322/4\3635W\261\277_\270S\257K\256\331\277Q3\310\260\377\304\256?\000\277\307\277v\213\260\277\233OT\205v\347\262?\321\332\303\212\3647\263\277\334ED\216\223z\251\277?\303\336\0133\266\237\277`\314\223>z\022\230\277\r\211\274o7U\256?|\203t\312\377|\261\277*4S\3660J\243\277\337\002AL\027\016\313\277\260(\250=\260&\240?x\337;\255\027\027\241?@\243o\030\305\331\225\277=\275\344Z\366>\305?j\257\302\355\260gx?Xb\356^L9\262?\306\323\034\003\234\n\273\277L\326\370\001\031\376\275?;\014\033\242\377\252\252?5\n\265\312\270\006\221?\273\345d \214\"\236??B\246\255fB\306\277)\310:\rT\225\247?n\342X\270O\272\210?r\354u\272\254\241\301?\206\272u\255_X\263?\006\266T\361\371\224\313\277Guv\321_\272\322\277\313PY:\255\'\242?\333Xt]B\237\244\277\205\321h*Y\r\222\277\325p=\270[\340\227\277\347\256\277\031\212\351\267\277\204\352\207pu\025\246\277\3153\222~;\255\242\277k&!\307\207\037v?\000\025\327o4\277\242?\253\315h\227f\312\313?\302\311\314\312P\333\325\277$\014\326\377\246\005\264?\336\007\375O\277V\266\277\023m\356\346\354\343\274\277Q)sD\3729\211\277>\255\346Z\337A\242?\266\020\331\334Ze\260?H\010\2323\026X\242?\274\205\325\021]Y\237?\275m\307\014j\332\246?*\213h\261e\332\304\277\215H,\024\356\352\271\277\367\211\036\265\022m\266\277m\3453\220/\310\271?F\253\230\2019\236\265\277U0\225%x\356\256\277\260dN4\237\005\313?\335K\373\302\212\215\320\277|\226R\2330\372\232?aW\223E\342\202\251?>1]Ey\377\247?\266\216\370\034\263\361\272?\270\311\237(\036\320\272?\352\366\266\227\343F\247\2777\363k\255s\361\301?J\271(\210?\212\271?D\224A\3661u\260?\007lw\301I\257\255\277\352V+\013X|\306?m.\203U\231\r\314\277u%[\303\254P\307?\013\031t:\364S\304\277hwfB\021\263\261?-N\340\022\276>\301?&\201wJy+\273?\347\033\222\253\261\267\237?T\275\252y\274\351\307?\306\251$\221\370\003\256\277\350}G\235\271\207\304?\374F\3759D@u?;\363\257\227\232$\257\2774[\305B\315\367\274?|$;\363\261\364\252\277\3152\227\265f\005\271?1[\313\365Q\322\253\277\'d\334\230\331m\315\277\235\27289\347\264\276?\2101\255\332\223X\250?\261\002\331\0030a\206?\320\257\336\364\263\r\257?\013!\260\335\352\234\213?\306$\204\236\037\361\311\277{<\362Y\257-\214?\007\222\362\306\310\356\277\277\277\367\314a\331\302\301?g\317\021o\220b\234\277\240\227\237~\274\232\223\277\003f\373\275p\362\222\277/X\2113\230\254\305\277\302\304\030~W\326\301\277\324\250A\262\231\223\253?\271\302{\010\372\345\301\277\231\013q\262\247!\301?\256\264+\247\264\252\224?.\014M\002\2176j\277\363\223\277\350\273\327\274?\331o4F\362l\266?\213\212\007j\306}\212\277H\237\023\rY\225\235?\253\376\311-\367\344G\277\037\200\325\256\202\257\235\277\027:\336x6\r\235??\002\205Y\310K\271?\t\302~vk\035\225?\372W\'U\314}\244?\243N\224;\nQ\311?\024\316\016\031\212#\261?\330\330\314`\267*\241\2779\\X\245\351-\221\277\030\333\017\301`%E\277\017k\301Fx\275\265?\225\351R&)\324\245?M\nq\361\022u\273\277\035\026\342bI\331\303\277\010T\370 %-\262\277]Y\240\216j7G\277f\351\371\235=q\217\277Kk[i(\004\327?\355\333\204\320\335|\274\277Z\225\350\224L\017r\277b\023\302y\0101\232?\"\371\212\276\230l\310\277H\370\310+\247-\272\277\002\232\360$L\234\255\277\254\201\256\024\004F\301?\"v\301\314\225c\301\277\303j\230\331\216,\270?\331E\244vG\026\252?T\370\247\202\003<\300\277\246Q\361\300\210\200\250\277&\362Qk\310\220\306\277\320gx\021\345m\303\277D?\007V\"\347\250\277\324>\370a5\356\276?|nOZ\332\216\246?\257\264\366\260\371?\244\277F\232\366tC>s\277\304N\204k\204u\242?\n\350\327\0212.\305\277\356\324-$#`\273?,\264\034\344\036\206\276\277\306{$U)R\327\277\274\273\314n\334\261\222?\373H\304\003\027\366\260?\342Lsy)\014\274?\363\2651\341\356\212\331?\311\334r\234\213=\274\277\341\254\352\273\3272\245?\342\302\030M\376\300\253?z@\344I\232\372\234\277T\0324\350>D\245?\037!6Qz\005\223\277\222\321\323\006\206\252\267?\262\2437\354\300e\252?\256\276\321\220N\307\212?\n\001O\023\366{\252\277\316\021p\024\314\035k?q\222\353\310\215R\300?%\233%;d\302\240\277(\261W\320|\371j\277\273z\304~/6\242?V\'\023\206U\350\275\277[\022\207[\265\202\270?\316\216<\0228U\270\277\315\367\321\355d>\273\277n\tF]/J\241\277\017\323e\r\261\203\323\277o\002o\352\2562\264?\340\"\204t\\\340\271?\237o=\307i\324\277\277\301\322\346mc#\273?\021\2106\213\257\210\274\277UT\343]\301 \316\277\234\024!.j\356\315?>\202\230\3058\003\276?\355\265\316\301\242(\264?\n\357\250\272\300\246\310?*\311\342}x\022\263?}\304\027\0226/\321?\221\252\373D\277m\257?0~\253v\211d\311\277\r\037\326}\322\tg?\302\272A\203\360$\316\277\373I\212\243\323<\320?\222\033N\036\326\267\243?\356\220\177y)\206\256\277\210v\204\354\205\237\311?\247\215SJ\324\357\254?\254\345\273\023ph\210\277\206K\264\022\332\334\255\277^\221]_\340\224\235\277\177Q\001_\327.b\277!/\216\021<\251\257\277\014S\223\326\003F\215?\n\313\204\021\241\237\266?&\262\"\246\222L\255?,;!\262ax\257?E2\324\270\362\013\311\277&\304\272\251\202\363\261\277\361vQ=\337\310\261\277~P-\352\375\203\261?7\257T6\240\221\250\277I\337\367@\270D\256?\242Y\270)\247\216\272?>=R$\364wJ\277\321u\242\341\206>\313?\017\277\366\177F\214\266?\265\266\244\203\013\034\255\277Cb\013\233O\302\252\277\354\250!\240\036v\222?@Fe\352\3669\266?\271\002\202\262\004\005\311?#\3150\036nC\222\277M\215{\000:\260\303\277P\342\212\362<\200\304\277t\327<\272\2241\304?X\217dA\221\257\253?\023\022k\320\200u\234\277\353\333\230Lk\241\270\277t^-\344(\265\304?0\007\202\025\367\034\261?\023\"\264\362\365\253\244\277\230\277\227\201\250\273\256?P\032\372n\377\224\247?\021\3409\377\262\343\251\277\230\375\357\312\030K\254?\242\316,U;\273x?70c\2344\263h?\035O\006r\033\026\313?\271Ce\330\203\021\301?\375\312\226\231\335\364\235\277F\343PDf\016\261\277\311k\227\245U\\\267\277\335\320\234=\271\"\315\2774\032\006\226\337\204\224?\3574\224\305?\277\264\277\246;^\016\004\217\261\277\320\270\351\266D\202\264?\021\240\315\361FW\223?\237h~\020\027\330\275?L\261\362h\334\274\241?\270\217&\024\272\235\220?\231\2325.\352\204\251?<x\212;( \256?\"\321\202K\375\2650\277V\\o\310\306\211\264\277\206\322\215\336U\032\262?Xi\024\266\277g\316?\303\007\326CY\\\302?\023\000\254d\306\031\227?\314\322\221\363\307\227\241?\207e.X\330i\260?\344\260%\3109\375\264\277n\025QI\233\342\263?\351\361imPB\264\277\3745\355W$\253\265\277\207\311\204\005\306\320\237\277\"r\377\335y\035\252\277,\332\323\371\205\355\275\277M\231\212\006\235-\315?\275~\222\023 \221\225?\343\354\027kSV\310\277\200\204\341q{\227\243?\036\321\362<ov\262\277\252\210\340\235R\n\300?\321h\226\220\344B\240?\320K\243\245\024\262\250?\007\34125\355%\260\277\2445V\303\331\333\310\277\003\323\267\263gH\260?\330S\341\366\302\355\317\277R\td\274\305\357\243\277\242\334\354\2412t\227?G\254[s\323\350\247\277\005~\310\355\274\033\231\277\023#}\320Wj\320?C\255-3\'\377\213\277\313\334iJ\357\206\272?+#r\2502\330\275\277x\253\r\335\267L\244\277\376\224V\220=Q\301\277L\302\037jz\316\305?\346\314\024\234\276\247\226\277\005\032-q\252Y\240?\257`d\257\033\032\310\277\032\301>\001(x\300\2772\364\376\274G\355\221\277$\t\240\224\267\244\312?\341\023\301N\275\277\251\277\236s\273f,\n\317?Z\234\372H\271\225\244?\216\203l\335\354\271\265?*\322\216P[9\307?\'\370\335o:`\301?Mw\326*\036z\262\277\266\303p*\375\342\325?\261P|\377BE\274\277\364\302P\367iT\301\277\006\367\306\013\207r\274\277\355\350]\037\363\237\311\277=\202\300\250a\237\200?\226i\0261\327\255\300\277\265+\034}\0162\255?\022N<l\023+\254?&\256\250\350\231[\246?\210\n\037Rk\333\220?m\"n\347N\346\305\277\364\242Iq \337\243\277e\307\320\246\201G\261\277 \2473\317\236\347\225\277\271\310\232\215\276f\262\277y\276\324]=V\300?\2540\035DR\347\305\277\256B\026y\316\316\320\277\321m\315\334\352\354\231\277b\nJ\254rk\265\277p\276j$7D\302?\036p\201u~C\303\277\2116\037\226\211\316\264?\245/\362\227\345\221\230\277\221A\350\340L\277\272?\002\037\333\216\360\335\254?\001\217\375l\253\267|?\001^o\004^\271\213?\001\236\0333+\347\210\277\221\373\312,\3420\265\277SK\352+\346\343\305?\327\216[\003#\362\273\277\020\247\0059\350\014\300\277\277\313<:\343R\204?r.\216*\217q\264\277\023\371|:\320\304\250\277X]\226\212}\342y?\242\376\215D\002\027\271?\236\246\257\354\034\254\301?\246Ww\314#\030\267\277lx@\375\374\244\266?&\257g(\340)\267\277\324@\'\253=\245\262?\360V\r\255\371\010\217\277\300\0200\302\035\025\263\277:u\031\000.\224\243\277>\037\312\367J;\256\277\352+q\242\225\337\232?\210\372V\023D\023\263\277\343;\344;\343\324\257?\347O$\376\033B\301?1T\035\312\024\361\256?<\312\252b\022m\301\277NT\326\007<O\245\277@\227>\272H\301\303?\250\221P\243\223o\246?\275\365\007\315\366\223\245\277\004A\217\313w\300\303\277\017\tx\377\013\354\225?\t\234\357\331l\217\323?nLE\346s\376z\277\345\370\000+p\362\231\277\301\230\230rc\376\305?2!~|[\271\224?\212Sk\264q\327\230?\220\0041\267;@\231?,\262\235%h\222\236?/\317\353\003\241\300e?\214V\221\r\217b\271?\374\313\\\370S\022\305\277\027\357\237\023Oh\260\277\016-\\\313-\372\260\277\210?\256\031\211\313\305?\233K\334\365\301H\304\277\304\310a_vg\244\277E\337\364\237\323\312\254?n7}c\244\343\256?\327\306\230\253\330\323\235\277O\000\260m\357J\257\277\222T\032\202\244\030\264?Te\327\215\315\247\265\277\264\037e*1\002\221\277\370 -\362U\275\302\277Q\032\032\005\241\267w?;\004m\006\3166h\277{E8\233h\220#\277|\242\300\013\"\344\321\277\210\375\226k\264\267\304\277\252\274L\326\254\320\257?I!\010\360\3172\262\277\353\343\252\2455Y\263?\335\364\211\334\227f\232\277\"\311\302\323S\023f?\250\246\325\356\213/\235?\365\272l\357\3109\302\277\303S\005\025&\004\306\277^\211\210\371\\R\245?D78eQ\210\300?\200(\260a\020\014\220\277b\256\220\367(\177T?\332\253\311\274(X\302?,\253\0311\245k\306?t\347\2148D\371\251\277\346e2\305XD\245?\000w\252\007B \251\277\266Y\'\023\211\010\255\277\274\332\002v\205\252\240?Go\"\274I\363\245?I\253\201\342\322\313\277?\214V\013\222\373Q\271?}\222@\t\251T\270\277$TW\032\316|\305\277\363\271\255\234\223Yy\277\211^qOt\332\277?\311\2242j\207\361\243\277\203C\006~\320\026\260\2774g\230{\225r\265?\274\033\342\023\361\277\322\277\321\345\030~X\261\301?O\344+\252\262\226\301?\237y\227\3250;\271\277\302\370\312\020\207\225w?\016\357\311\3564+\242\277\215\2438\225\315\361\246\277\342\001UI\301\225\252?\353~\254\326\313\027\206?\004?\021\266<\t\212?\376C\331\303\320\252\265\277I\021\230\302A\330\226\277\324h\375\225\222\343i\277\363\225\217\034\370\360M?\025\225\266H\316N\247\277\255\335\230\377\\\037\270?\320/\305Qc\'\270?Ue\006Hr\026\262?\021\345!\250\331\234\232\277#\344Z>\341\257\301\277\225;\342\356\3250\237\277UA+D!\235\233\277\004\376\324\220\304d\305?\010\250\t\371%\010\263\277\004\271Fb\342\227\244\277\200\241&!L\335\320?\363c\037\245\365t\270?a\215\267\302\353\036\014\277a*U,\342C\272?S\362\3071\205\224\307\277k&A9\302\201\263?\037k3\030Wl\226?\336\230#\243\303\307t?]\004\005\220\000\217\200?\323`\360Lv\010\247\277}D\332\212\354\356\311?D \240\234s\236\257\277V\264\303\366\243\365\200\277\316\304\032x\305\213\230\277\000W)\031\230\022\327?\301\372]>n\205\217?[\224Vm\221g\222\277O\311\224\376\224w\301\277`[_Ya\203\245\277\327\267\022\3700\014\302?b\324\253\000\320\243\234?G\345h\234\374\013\257?FY\r\252\337\330\321?6\327\034\304\302\254\217?\035\023\374\264\357\013\274?\266\351\311~@\347\315\277\223&-9\001d\206?\204\013\020m\'\320\254\277\365\371VZ\265\277{\277M\\jlV\237\217\277\343UXrs\363k\277\377g\371\260|\177\226?\304\203\243\237\330\200\301\277\000\320I\276\"\346\271\277f\007!\223Y\373\260?\202\313u\234R\310\253?\275f\365\311\001e\261?Ph\340\005z\217\301?\016eo8\376\000\245\277\233\346\374a\307\347\277\277b\350\2263kQ\233?\321\301\353\350\220\357\251\277\274At\023Q\256\214\277T< \260]\037\200?z=\236\346s\010\265\277\310}\355:\306\260\233?\324\320\350\240\023*\245\277\030\305m\007+w\300\2775jB\017A\251r\277\310\365hZ\204Sd\277\344\3126\337\033\311\266\277\217\021\245\326\0071\224?\240/v;T\256\263\277\246\245\231\237\376\343\226\277\236\330\237\\\001;\255?\033\371\221_^\232\270?\361d\032\016\206y\304?X\033\216\336c\373\260\277d\317\020q\210\250\201?JP\014v\373\366\273\277x\025\244H\250\216\266?\225\270\330&cQ\260\277\014e:\305\262Q\257?\262\336\332I\244-\267?YcM%\312\243\317\277\354eF\000\014\203\227\277\333\\1\275\036\026\246\277\026[\321\206\301\226\271?\0250\014eb\345\210?\364\316\323\206~\254\224\277I\265\250=&\017\250\277\364\352\016%\334\324\204\277\221\330\315>N\206\273\277>\365\375W\375\330\266\277\004\006B\261\007\242\270?\357P\354\271\234\275\252?5\207\377z\355=z?\332&8\366\216L\232?\346\243\230$\244\007\244\277I\222\264<\321F\255?9\230\354\230\301\024\255?\034\360\265\314\177g\245?\212&v\264\225\332\266\277\351\303\257\030E\243\251?D;\264\243\"\254\304\277\356\270G~\371\336\216?\177S\325[\220\340\220?o\277B\301\342\200\232?\230\243\233`\275f\217\277HMEm\345\240\221?\344\014\324#\005\361\273?\341r\221\370\336\256\262\277\224\275R\347\374\233\270\277\321A\262v\201o\251\2771\365:\215e>\275?\36532-\001\237\275?\372B\266F\0346\304?\3754\326a\201\262\212?K\027\320\351\004\272\267?4\235\316\334\310\t\276\277Af\272\321\245{\257\277\025hi\357w,\253???\210c@\026\265\277\227\337,\357\020\300\251?\356\374\305t\245\250\301\277\352\263\004L\313\002\204\277\027\237\3574?\216\240?>\265\014\213P\333\222\277B\rP\213B#\242? \324\027\301\n\314\300?\321\364&\004\254\233\225?s4l\354~@\261?\020\321\306\005\033+\310\2777k-,\304p\262?\214\177v\336\235\277\265?\244\230\255\377j\357\244\277^\377hO\320A\242?`\251I\366L2\272\277Z\241vew{\241?Yo\001L\207#\266\277)\370V\264d[\305?z\342@m\311\026\221\277\3163\240\021\343\253\260?\374\240\362e\332\026\244\277\036R\252b\245+\220\277\262:V\224\021\n\236\277[x`X\334Z\314?j\266\372\267\373\324\311\277\035\\\013`^\354\256\277\036^\024Q\301\376\n?\365\207\216\230\025\030\305?A\273H\201\225ea\277\203\354*>\364\247\226\277\271.z8\342~\263?\305\234\020\336t;\306\2776\301\212!\005d\251\277\353\371\233\250XA\267\277\230\370\231I\242\374\266\277\352\330\332\026\340\334\265?\3147\037\036m\257\301?)\302\260J\345\034\277?\311\034b\355\017\027\271\277iq\017\0379C\223\277\221G\002\307\243\035\222?Z\302\361\247\244\030\305\277\225pm|h\036j?\01758\300\271\024\253?\302-XJ\016\241\300\277A5=\271\376\314\220?\333\356\260\000<n\230\277\365\230\246H\307R\273?Z\274\032\307\323\317\312?<3`\323\354w\310\277*r\345XM\035\277\277\032\256Nc\355\304\267\277u\367\245@\333g\262\277\n\275\303\026I\344\273?\247\364\226\342\377\325\246\277cZ\212\334g\307\314?\217\201\367,[m\243\277H\234>S\216\204\274?f\322\342!\232d\236\277\243\216\313\377\322\177\250\277b5\234-IH\321\277\352\324\365\317W\257\300\277FkA\0358+\301?\240\214\353\273\243\274\305?2Mb\004f}\234?\034UKP\\L\314?\217\207~\255\224\306\312\277\251\361\201 \013\340\240\277\276\273\323;\344\353\303\277\262\2042\305\251\303\230?H\267!\225L\326\320\277\310\230~\310_\313\273\277\333\233)Uj\202\303\277\371(\206N\022_\324\277\005(\271\300\217\262\255\277\326\245\254\240\275\005\301\277\371\263\006}C-\205?\211\333\255X\324\246\302?\207Q~\310@W\263\277\236\312m\253\0072\233?\372O\005\356\263b\340?KvMdr\272\270?\'\227\312\035 \272\271\277\005\332\261\027W\\\221?d\304\037\376\371\260\301?\3146\305\313\031\'P\277\004N\340dK\004\246?\016\010\340\372\327\003\302?\364g\362\373$\024\265?\255\356\242M2\345\276?\345\233\001\264\362p\321\277\214\323\303a\257\300\220?\327\223CzH\342\220\277c\'\370\351\267\253\266\277\207\302\025\025\241:\264?\336\370\024\375\277\326\301\277y\347\245\3665\317\247?\274M\326Pm\327\234?\263\321d\214\232\317\236\277\372\315\017\321\256*\206?&^\234\364\343\340\265\277.\036r\307\3426\277\277U9\026\202UE\261\277_\tu\356\336K\267\277~\226YQ\316p\325\277\277\371%\312\020l\211\277\377S\354[\213\006\260?\000\242\267\005\204F\253?\336\372\327\310\334\357\302?\242Aq\272\'\214\212?\374\027\207\360\200q\310? \325\242\'o\367\270\277\010n\004\265W1\274?\207\233Ce\336\273=?]^\365Z\210U\261?\210H\376\270\252s\310\277\317bdq\375\007\205?\037\331(\213\327\356\302?\373\327E\010\000\314a?W\010\002/\230\305\250\277\177a>l\364\201\244\277\034\222*\350\222:\275?\2502\354e\032\246W\277>\203\273\243\304\304w?\306\353\347\303\t\214\265?zN\034/C\271\276?\346\264\341\230\264j\247?=\031;\213,\037\304?\367k\200\277\343\356\250\277\333\216e\273\261\201\303?\220/&\221\217\220\275\277K\232\017X\313|\244?x\377 dr\222\306?X\006\226\243>\322\265?8\215\325xfs\303\277\203\307ecQQ\322\277h\214\315U\231\206\244?\342\235\0350\221w\234\277I*\222\247 \230\311?\tY\0033\035\213\317?\362\304&y+\346\242\277a\220\255}\304\013\302?\276Bn\005g\312\300?e\273\222qhI\274\277\321\354m\000\337\217\267\277x\021\"\316}\316\220?/\236\336\373\372\212\333?c\222\203!SbB\277\353\007#\311\343\254\234\277\220\373\004\225\243\006\264?y@N\300\340\355\330\277\355\005\365_\2625\267\277\352\n]\026\2402\266?\255@\274\027\235{\260?EH|\364\017{\304?\371!*Y\265i\244?\325\2113-\215\245\271\277\317\034v\360\034\231\276\277H@\357\274:\017\027?\350O\\^U\270\277\277\\\266\265\305\330\007\314\277?\315\215J~\360\277?\302\322\317\324\242\"\275?9\014\263,\3200\303\277lqM\363{\301\325?\202\2211\036\364/\235?<m<\206~S\247?\314\021\021\216\024<\273?VS \277\001\274\301?\261a\255?rQ\241\277Y[\352X\375\371\252?\"s\"\346\037-\253\277&1\010\373`\217\257\277\016\005\247K(\241\260?k\330\265x\321Y\234?2\n\013\244I\211\264?\313\355p\325\317\350\246?\036t\374\273\247T\232\277\001\206\303UL\207\300\277\177\253\365\3360\213\265?\374b\007L\t\264\311? \347] K\346\220\277\222<N\270I\004\317\277\274\263\210u`K\300?hz\201\322!\360\245?^\261\023#\335}\303?1\364v\177\233\037\211\277Y\0278\347\034/\235\277\265\007)*\226\n\313?\004\257\352oR\252\261?r\323\246\341\331]\310\277Y\313,\252\366S\256?,\300\253\342\206.\250?[\226\316\225i\357\236\277E;$\246\023.\240?\211y\345\003\376\342\307?\232\000\252\000a/\266\277\020\357buM|\273?q\037\300\257\tB\245\277-\221\027\t\263\010\312\277\313\275\033\3628\316\221\277ih\'\246\212,\275\277\004\022\273e\021w\254?\367\344\367\205f\r\216?\225\036$\354MY\257\277c+mp\227\363\270\277s\275\361\027u\240\213?\025{\263\310\341\202\256\277%\032(\353\274\363\245?\347%\321h\224\210\263?XL\333(\031/o?\340p\310\2616,\265?(K\307\366$3\272?\023 b\035\360>g?\023\371\021\032\377\316\221?Q\021\276@\004\337\273\277\333X\234\303B\260\253\277y_y\356\235\262\270?\336\265\373\246\211Z\242\277\364\023D\034^3\253\277\177V\374\307\"W\304?$\256\265~\251\307\261?\246\247>f\244\333\244\277\022\371Md\220\275\306?\330X\210\217\262\002\250\277\210?#W\337\251\301\277\254q\342\2155D\265\277H\204\324\313\310\270\255?#U\365\337w_\231?\357\203\241\002\202\245\234?\332\325@5\026+\213?>wBv\032\326\262?\273E#N\224\360\303?^\240\216\353\313\341\240\277\317\375-i\317\034\305\277\302\341\325}\236<\263\277\355SL\261\3647\255?\000\242d\010J0\227? 7O\\Yy\261?\005\346\2272\030\360\217?;S{f|8\307?\025\315\326\314\216h\302?\322<\325\3676\277\240?\021&\314\003\322\322\254?<X\364\241\035\343\302?\305(\254\305Lz\275\277\345\244\207\313\262y\303\277\235\377\222^\371\343\273\2777\375\320\274\'[\340?\033.%\254?\221\242?\245?\221$GX\276\277&\013n\332\335h\276?\223b\234\347h\022\221\277\271\305t\377\\\n\265\277\024\225\024\t\342\346\206\277\034\255\203P\364\345\302\277\3727\214\342v\312\253?\244\017\312X#\361t?Me\260\337\324\354\310\277\237\003\330=\212\030\242?S\317\010\327\014$\217?\353\344\266\r\314\036\220?\211\004L\321\014\351\264\277\311\020 \314\304x\254\277\0366\245\036\004\234\243?.\212\342\024\325\227\233\277\372\310\030=8\214\241?\224M<\316__\224?Dsd\026s\302\177?G;\241o\261*\277?\252a\325 \364\257\220?5\037\276\222)\315\230?\325\302\305\002/?\235?_\245\200A\343\021\230\277_\310\013\317\367\205\213\277}\277\265h\235\313U?6krS\216\371\301\277\271\333\022\365!\r\250?\273\t*\230\320\315\257?D\336\365xX\263\257\277\3707\037\035\002\273\305\277\257\211~\324]\356\266?\316R\372~\217\243\232\277k\342}\027\3434w?\204\351\377Khw\254\277F\210\212:2\204\265?w\013F\366X\006\253?\007\020\345\270I\330\277\277\237\255\025\217\353\317\261?3\216\025\236\375\206\272\277\035\325j\274\022\337\331?f\335,\265\344\251\244?\222S\374\371\373\214\305\277\321K\275\226\325\037\321\277&\243\3544c\224\253\277\337\024D\014\206l\242?U\304\361\215\261\310\301\277e\257\331i\010\235\240?4\235\363\262\221:\272\277\367\246\251H\0071\256?\036\355#8\336\350\305\277\246&\'\335\007\257\270?H\360\373\357\273i\227\277p\327\350Y\222r\315\277\346F\241\325%\'\236\277CyH~\321\004\216?f\224\003M?\302\272?_\201-( ?\307\277\326W\017\375\216\361\305?uB\3059tV\246?\212N\235W\235h\275?<\273l\034\336\206\275?B*x\025zv\251\277\300\216<\303 \264\232\277i\210\343\365\366K\327\277\212\251\341jD\220\261\277\310\276\307\245\265q\276?\356P\344\335w?\223\277x\357\037\304U\032\301\277\224G_~\311\275\272?\245\221+\316\366z\256\277&\371p\204\245\023\220?\370\335]\334\206\335\246?L\021\273\260\347\357\302\2779H\275\027\353\014\247?\315z\343\333\274#\256\277E\232\265\037\304\256\262\277\247\021\233[l\311\266?=\262\362\002\220\367\266?2\257\033\nYS\275\277\342\266B\275\325\341\232\277\321\246\336\264.\234\300?\252\361\234\3306\311\300?6\035\247\037\224\010\231\277\330\275y\034t \264?{\362\270\346\275\211\322\277Qj\307\023n`\273??Y\027\206\211]\264\277\327\372\344p\333\232\260\277l\372\351r\233\274\275?\0075\263V\371\317\220?\333{.\346M\370\246?\216:\253\252\212\267\275\277\177C\345~\006\335k\277j\202\314v\205\316\231?\207\261\005\367\366\264\270\277n\177\026\264$\310\227\277\365n\232\243\241T\254\277Pu3\276R1\264?_\017\2452\305\324u\277\\\240n\377\010\257\311\277y\372d\316\031\337\301?0\"x\265\201Bt? \275lc$\341\302\277\345_O\240\320:\300?\031\371\355\322\026\222\236?3\002\325\005\371\335\217\277\363\241Cb\230\200\302\277\314\027\224\371\205\224\227\277\236jG\025\337l\306\277\206R\177\345\340^u?C\205\nd\257\353\207?\025\245DN\270\'\242?\356E\302/7%\244\277i\027\034T\302\016\272?\336\021mLC\343\202\277\'Y\213\227\r\247\304?6\017\221_\236\363p\2776Y\013>\313\203\207?\215M\367b\265\226f?\006E\227?\006\031\321\277\360Y\362\001We\265\277\336\031,\311\203\335x?\270c\331\"\222\037\273?tW\361M\035.\320?\033&\\\310$\370\241\277!f\002[\217\272\315?\251m\321\350A\021\272?$\001\016s7\202\261\277?\376_T\325\210\256?\335W\263\001\017c\257?\000:\351\313\0310\256?\304\256`\207\357\004\270?\207\273\324\347lP\261?5\016\264\031\365\n\314?gt)@6*|?K\215+\256\251H\275\277\275\2472\323c\317\306\277\324\271\251\314\'\336\314?\222\346\240\'I{\262?n\025\326\351\205+\240\277R\003\314\206z|\263\277\032\241\337i\025\346\271?i\305\003\3026\003\264\277\253\233\326y\270\211\257?\026\214\311\374\000\010\277\277\266\026EYO\246\240?\363o\2265a\370\320\277Qu\177\220\344@\251\277^\013\361\371U\261\315?\206\321o\336N\320\264?6\030\007\033<\310\307?($\227\177\r\215V?X\253Ce\313?\251\277\316\335\245\336\364C\261\277\370%\224\372\276\377\210?\020\205\021w\241\356\300?\230\320\377uT\304\302\277\232<\017\343\275\006\260\277\326\206\001p\205\325\271\277\331\203\234\212D-\307\277\345\321D\314\017\035`\277\221o\315\211\341\223\300?\342\303\201%\177\334\242?\250\241\313tW\307\274?6\245r\3018\210\273?\242\022\304\373\353}\313\277\257\341r\352\310j\250\277\254\026\246|\017\212\222\277\032\000\tk\311\352\312?ZOE\250W\005\315\277^\\\362Nb;\221?\trc\206\201\n\240\277\221\373_^Zs\222\277Mw\'O\320I\220?\032=)@E\016\242?\021\352\223\245\000y\320?\370\357\344\n\204\256\244?\200\026\356>\362\313\252?\\Ls}\262\250\241\277y\247\255H I\306\277\211\200Z$@I\221\277\257\310\204\335\307\240\270\277\246D\315\226\327\025\301?+s\374\312\2608\306\277c\031\\\313\020\355\257?W\234\002\022\230\222\271?\354\347\374:\343 \307\277\016\263\337\325\370\216\250\277\030\377\250\r-\327\300\277\331\222\241\230h\010z?\023\331}\306\304\200\261\277\265.0\210cK\305?\251us\351L{\305\277\215\231\014F\323\326\325?/a\251M\243\213\233?\3657p$Uz\307\277?\267\013\265\371\244\254\277\341C\016\034,N\300?\214\366\336@o\260\260\277e6^\354\362Q\267\277?\300\224\307\353\325\243\277\246\361\254\303\352a\226\277ZW\235\001d\004\273\277\023\375\240\202\024\252\211\277\242m\277\261\260)\304?%\313\321\032\3022Z\277\207Ud\367E!\311?/`\232$\005\301\321?\201k+43[\235?w\302Ot(\351\263?\355\312t\343U?\301\277Z\367\253lo\357j\277\003q\032\3656`\250\277E\327\207\220\353\370\265\277\352\307\357\006\232\366\305?\354\374\017{F6\267?\260\216\306\274\247\274\230?!o#\335aW\270?\032\371\\\262\\\355\246\277\313Da\026I\350\212?\255\254\241\332\261|\244?\355\013\302\304]\340\310\277\344f\310-i\303\225?\357\374\351\316\301\334\245\277\037C\366\004\314\314\311?\003\027\371n\243v\261?B\265\223*\014z\300\277\302=\375\357GM\263?\344\261\027P:\013\312?1\212w`_\312\307\277v\246\272\276\250\177l?\210O\372C\317\264\225\277\016Gv\331_\"\262\277\n\033\327\361\206\320\243?F\335|\026r\345\312\277\013\3218\241\251\262\220?\035a\211\270n\003\253\277os\376\2325\276\267\277\344;FclC\277?!\022t\235\004\001\300\277:\022\331\233T\312\270\277\211F\272G\274\023\271?`\007\244\334\023\332\240?_+E\220r\025\243\277\262[\330\223\013B\265?\347\265`\001K\230\224\277\3562\005\247\355x\240?\306\366\330\027\230\212\243\277\257}e\237\3330\266\277\246\010s\'g#\302\277\362Ib\273\2577\232\277\357\376\340\234\217\242\312?d&}\343Ou\320?Z\036\210\036\341\222\256\277\177vU;\314?\226?\271\306J\255\207\251\263\277\311+8\010\207m\300?\340E\215\251\223\030\240?F+\351?\377\214\307?\036\272\004\032t\303\301\277+\271;]\234\032\256\277\035Z\210\227\244iv\277\232\271\006k\240 \315\277\306\371\037\235X\321\327?\305\214\363=\237\236\261\277\243\233\'\235\220\244\254\277\002\373\264&\2235\317\277\006\313\373D1\207\222?\264\"\362\204\233x\315?\\\t\227\016#o\266\277s^U\337\017\355\253?AGQ;\274\327c\277\207\305\\\006\026\367\206\277\240\263\323\236E\302\255\277\375^\241\213\301v\264\277\210=\260\317\344\221\257?C\364\207c:\\\262\277\'\315_E*J\265?S\252z\363\227\232v?O\3445\020\002\344\300\277\342Le\2436\025\320?&\312\037=\2223\275\277\233Wv\027s\235|?\231\025\273\201C]\266?\023\363I\270<g5?\310\372.\002f\307\213?7MG([\301\246\277P\324\307\211\245\276\273?P\276\327\241\205\215s\277\305\307!\001\022\252\251\277]\232\262\277\223\377\271\2778\376\033j\025\327\317?%\210m\365Fg\222\277W\2362A\020\200\237?\320\343\237\247\211\035\275\277\375\312\352\335\333\276\267\277\017\305\3211\014$h?\276\2246[\035\312\300?\231\'\332C\263O\260\277\255W\302\353w\006\270\277\323\356\020\004\023\205\245\277\233\033o8t6\303\277\226I=IDz\267?\034\247\321\":\257\242?\033\214\207I\256|\263\277\233\231Y\340\314D\265?\356V\353\346\265\007\204\277\\\214\237\356\025\332\271\277\001\210\003\\\300\231\310\277\031\034O2YQ\312\277\237\316|\367\270\010\235\277HR\222[\274\236\301\277\230\177R\021\374\020\236?\366\306\n\234\337(\262\277\212\234\216\'\261#\222?9E L\244\t\243?\355_+[\361\377\240?0~[[\007\246\267?Tr\346\253\213\330\305\277\022\032n\256\035>\277\277\213\372/W\006\014\220?\034(C_X\240\265\277\257\316e\tZ\272\317?c\253CN\261v\266?\240\366\345\216P\n\251?\035D\267\276F\t\254\277\244\234\306\302\243\240\323\277\031\254V\375\362\321\256\277\032]\337%5\243\307\277\311.\323\200\341\376\262?\374\230\323\r\213l\264\277t\353T\225-^\202?(\372\014\357\351=\263?5\256\0068\213%\323?v\263J\026\025\024\237\277\341\235\021\200e\363\240?w7\277\351\275*\301\277\244\325at\307[c\277`g\230\234\202\257\240?c\3221\207a\336\240?\217\325\225\3732 \321\277\371Pv\351\302\372\240\277\215_\315\221\014)\244\277\314\247\243\367\036\273\271?~\246+r$\372\253\277\276\310d\201\321\177\304?\000\231\273\342s\343\310\277\"\310\231K\3102\316?SN\260{:\\\277\277\340\332\r;\237X\264\277\354\351\'\2300\226\220?\333\207\222 $\300\243\277X3$\232L\264\267\277\324!Gg\332\205~\277\033_\306^)\n\331\277fw}\365\242\347\263?\007\032e\266\377:\246?\355\031o\022\244\254\314?[\334,\036r\200\231?\2773\235c\302\314\261?\276\347.\374\377?\245?=\364\017T\273\304\264\277\326\254\022U\373#\216?\023\312O\325\354\002\302?\326\375\366\374\026\254\276\277\2579\177\030.\231\274\277\304\014\003\210\030/r?\344h\206\226Q\372\262?\250\007%F\243\026\252\277\331\022\277\326\300\037\177?\2104g\276w(\236?\230MS\313\334g\271?^:e}sv\223?\236\016\221\334\331\301\307\277\257F\322\333&h\316?\245\003}moB\320?B\317\365\211\"u\243?\250 \361\345;<\223\277<;}\353\314h\310\277\233\205\373\035K\027\262?Uw\246\362\243\235u?,f\300\263\3521\250\277Lw\331\004\356r\304\277.\036|Kz\351\220\277\004d]\300\t\033\255?gI\264\"8\033\234\277W\230P\265k)\253?\272Kh\014B\351\200\277\315\013\303\244\0300\265\277<K\201\220g\337\275?8\250\001g\344\022\260\277\237\260\317V\027\375\254?\300l\021q\335\224\243?\270\312\332CY\351\205\277\371|\377\036\002J\251?\211\224Y~:\307v?\256\243\374\317=\024\301?\2216\373\317\255\274\275?D\206p)\227h\263?\361\361\332\214\260\365\332\277\200\342\210/\275l\247?\025=\366[\317\301\262?\303\027<7\334\332\253\277\275WG\307\353<r\277\365\323\310\007l\005\202?\376u\235\227\347\323\242\277\r\177wD\257H\310\277\352@s\256Bx\277?_mG\337\t\024D\277\232a\264\272\031\355\304\277C\2002\362=\244\316?\253m\321\0017\255_\277jNS{\347\203\315?\366\035\2656\225\366\304?v\207\255%\212\264\252\277p\2169H\230h\310\277in\"w\243i\321?.F\327\237}e\302?\023X\352\344\355\242\312?\037q$\335JK\245?\007\363\034<\201^\307\277q;\272\005\316\323\306\277ql\010\331\334\317\206\277(j\017h\271\273\232\277\354\275\240D\361\006\314?#\376\236\350\030\264\253?Y3\020V\202\274\277\277D\355\303P\223\246\250\277\341O3\206\307\323\216?h\021\263\330\'Mz?\362\363\031i4\226\306\277\215!\014y\314\004\312?\022^p\342|$\264\277\317q\273\006\021\337\245\277\305\255\355\375;\306\213?`i\325\207\037F\301?\351\277\360\374\010\024\205\2777\223\247\357\250\026\244?\257\376\0343\027\234\276?\231X:\265\377\373\303\277\006>=\241\350\352\240?\005\2240\246\220b\310?\217f\227\214F\211\261\277*)5\220\036\201\300\277\247S\347\027\273e\307?j\216\007\321\322\323\311\277\350\250A>Tm\263\277\267P\230\247\335\007\257?Z1X\365\261\031\306?\323\250\024\007s\374\263\277\342\300&\320\306\320\250\277\316\005n\330\234G\263\277\243\251nWOM\241\277\315m\252\365\252\014\260\277\227I\370\367\363\256\244\277\256}\220\037\264\264\305\277T\227\2465\277\314\242\277;\337\302\240~!\220?\270\177\010G\353C\265?@\277}\013:Z\300\277K8\203\n#0\225?\252\216z/\310\351\203\277\353M\240\357\353-\206?\274\264\t6 \334\310?4\313\t.\025>\270?\326\250N\254\277\352\303\277\222\245\341c\373^\277?\220Dx\232\177\321\271?~\372\225\225\363\305\266\277\023{\352\227k\021\266?\332\212\003\002\303\367\271\277\221\263\'\271G\323\264\277\366 \004Ib\222\321?fR\317\r\227\311\264\277\007\247$\031\334\301\231\277\266~\266>\035\247\276?\361\325\224P\023\355\245?\316<V\224\362\251r\277\314\310\331\332X\203\305\277\020.[n\361s\274\277\370\234{\240\367<\317?\246o\353\216\037H}\277\342\204\'\233\001>\300\277;JlvF@\322\277\0035V43\340\244?\033\363\307\363d\211\226?,\343\255DL\234\302?\027\330\221\315\035\234\301?\003\250\254N[\351\232?\345^\t\003m\204\245?tZUh\331\253\302\277\275N\2773\305l\300\277\021lb=\376n\263\277b!\247a3\233\266\277W\306\251\014\r\244\264?\324<\217\262\200\357\254?\342\"%;q\321\267\277\271\225C\334H\n\300?\310\265\344\304\342T~?/}q\010\271u\263?d\322\303\336V\031\255\277\250)\305\330\275\367~?r\253\215\237\025\356\233\277<\327\3404\366z\306\277C\303)\202\334\336\255\277\'\370\005j\214.\245\277\033\001\'u\256\347\304?\r`Rl\227\024\245?\301\316]F\327\000\260?eyx\242\225N\222?\343q\010D~U\267?\n\354e\345((\302\277\267\236Sr\360\255\264\277\316{\203\036\313\353\277?D\262\246\010z\026{\277\210DX\002\355\312\270\277\225\214\336<\216\214\275?\367p\320\347\214\307\312\277|\221\340\r\234\324\243\277\236\243\335\313\310F\261\277?\2233B\262\326\267?\312i\271\0058\314\215?\016A\323VE\377\224?k\375\332\350\321S\261\277\3744\031\371\324\353\233\277\326\342\320}\210\026^?\014g\247\3434>\201\277+\024\320B\375\260\267\277\202W0 \370\312\321?\345\032\025\342\016\220\267?H\336l\334\312\365\241??\330\266\205\242\247\320\277\234R}\206j\300\264?9u\322\014\245\346\264?9\260\211I\3118\303\277fU\253!k\374\303\277\330t\026\312\3720\264\277\021\273\376\230\307\000\243\277\257\311\263-\240\nY\277\233zQ\016_9\247?B\230$\210s\023\243?\272\022\336\271?\022\305?i\245\354\371\330\262\277?f\0073\000\245\345\262\277\277#\266{\026\253\276\277\260\000$\256\274\253\254\277\330\271\243\342F\252\241?\003f0\000\232\340\277\277Vm\006*\264Y\260?\205N\212D\177%\253?\316J\217\267\247\027\321?N\311\265\236\265\354m?x\215\223\332\360\006U?\333\000t\261\\\243\245\277\327\245\341\250aZ\256?\270\267\202\014\256\010\300\277s[\236\037\002e\326?`O\231b(\004\312\277\377}\037a\021\236\271?Wp\210\332\377.\267?\246m\020t\345\366\210?Z\241\335\217\276\224\312?\021\237\'\231\n/\261\277\244\2477L\247%i?dA\263Q\353\272\273\277\360\r\352\357\031\323\243?\3718%3a/\247\277\353J\257g\3234\307\277;xd\344\331\206\267\277\337H[-\t(\260?x\350\231R2\004\241?\330ZWL\2364\263?\017\220\246\271R_\266?.\221\357\377\3606\303?qC\210\252\214\\\323\277\312\t?v0\033\270\277:f\307\314\333\263\300\277\036\231R-\017L\256?T\032\346\254Q\233\316\277\371\212\233\310\243!\262\277\245/@\210\360X\245?j\256r\273\272\272\301?\016U\237\234(\343\264\277>\177vcTQ\300\277\\*\t\244\306\021\307\277e0\271\263r\341\326?\272-\214w\304\376\265\277#J\213\213\307@\305\277\014\300s\014v\251\243\277\333\346\340:t\275\300?Q\016R4k\177\204?\275\371j\212OH\302\277g}ajv\033\271?me\260_}\212\302\277\212r.\034\004\021\273\2774S\220fuZ\307?\0005\270\376M\374\244\277\343?\332\242a\347\275?\365-\327\274\265\025\306?\026\343T\240\253?\325\277\210\017\376\204\303\273\242\277\000\336\266\r>\255\320\277\320\033F\376\\F\233?\317\366y\253*/{?\r\277vS\343[\272\277\035\273djo\242\241?\317\326^`\303\331\177?\314#Y\"<\263\263\277\371\242)\306\340\370\240?\342\376b\347\025\350\261?xey\007\223\267\261?\004\215t\257\343\323\202\277\340g\307Q\370\017\227?\261\230\272-\024r\267?\027\254\252\036{\003\251\277\023\3318\030p\203\230?\245\356\343\333\367\024\265\277:\224\034!\350\017\240\277\321j\\\340&\025\270\277PJo\306\270p\214\277`\371\326[\t\002\242?P1\217\354\276\227\274?@\237\0329\302\022\240?o\022\216\3012\363\263?\003\031\317\266\305\350\242?\332q\274D\217P\300\277;+tB3\357\300?\356\242\377\325\346\317\201\277jeF%\300;\240\277\3130\221a\366\213V?^\241\327i\355\270\225?\t?>\346_\177\265?+\232\371T\030\214\207?\230\2335\205\216\020\254\277\265x\223<\006\033\310?!\247W\360\232Q\262\277\007=\313\275\204&\214?\177\225c,=\203\303\277I\021\224K\334\220\256?\344\007\177s\252\205\262\277\377\225s\206\032r\264\277m<\273\027\2256\205?\322:!\237E\374\256?\214?{TF\034\302\277]\022\214\354\364\316\231\277\234\034\225\003\031\240\237?>t.\276$\265\252?}\0067svP\266?\347\340\ru\351\313\314\277\021M\374\242\345j\213?\255\252S\314\267{\225\277p?\263\022\320]\310?\335k\275\202_O\267?0X%\333\324\'\213?\035\220a\010\300\302\323\277\377\227\372\211\007\327\215\277\331\235!\271vO\257\277\022[?\034\3113\276?b\030\361\030\272\274\250\27763\340\266\240\374\303\277\024MQv\237 \242?C\270S\341\306\306\265\277\272_^R\304\022\230?\\\1774\0236\323\252\277./\311\024=\243\265?\251\022\306\035@\332\275\277\025\270]\323\201\332\261?\321\036\000\247<\335\230?\016\237L\323\303^\244\277h;[I\"<\263?\252-\364\332\t\313\244\277\251\031)\271\352\334\276\277*\301\272\360`i\244?3\3475\273^\220\304?`\273T]\364@\264?>\251\372\242\336\013\224?\024\360\240\033\271\336\304\277\250\365\010\322<\354s\277\352\274\256)\"\224\242?2\341\346\014\365\254\301?\002#\330\301wV\224?gNJ\251\301B\311?\027\355#,\007\244\265\277\370me\305\314\030\272?\204?\204\334p{\272\2778\030\025T\221\355\257?\253\253&\242\352s\224\277\023\304\032di\243\251\277\241Q\357l\356\331\275\277\025\027\014e\255Z\244\277\341\231\301*\342\203\245\2779\251\2279\206\225\250?\236\016\320\005\003\r\247?d\177\026\235~\036\315?\\P\327\205\235\227\245?6\r\246\024\027_\274?v\373\022\313\342\363\323\277\027y\3225[2\321\277~\342Og/c\246?C\030H\271\203\006\247\277\255\203s\013H\371\240?\317\230[Z\031yr?\262\346<\303w\353\264\277\203K\026\363\t\336\266\277|\265\335\265\351\304\254\277cM\016\333\347\'\235?TJm\336\033\261b\277\014\350R\370\177\372\314?\242nb\327%\257`\277\250\355\"\203\347\367\237\277\325\202$\323x\'\301?S\350Q\277\322f\315\277\221\'t\365\331\263\250?\276\301\313P&\250\202\277\024l\362x\250\301p?\020\031\246(\t\003\232?\315s\362 \331\373\227\277\205\270Mb\265)\252?\344<gu\255\016\265?\3717\340\230\020\276\227\277\276\037Z\235*\032\234\277\275\276\235l\332\325\262?\037P\020\355\351\345\230?r\236\325:LO\265\277\266\264@\034,\300\275?\227\031\203\356\266)\273?\345=\302\366\225t\204?\001\031`\211\004[\261?\242VO}\020b\222?Q\260\241\236\027\353\262\2778\030\247\0163\007\303?\307\277\220\025\263]\305?\333x\376\350\202\226\310?5\035\224v\353\325\241?\016P%\210XX\250\277\" &\253\363\215\254\277\376$Bw-\262\263\277\272e\310\256\266\300\264\277#\014\307\210\247\371\226?\347gN\003Y\317\314?\232S\342\344\245\372\243\277\200\030\264\264\305.m\277\264\311\222:\336\014\274\277\034\003\271NK\"\260\277\353B\357\353\275\311\244?%G]\323e6\303?FK\216\351\345L\271\277A\1775o\337\342\266\277\\\376\220\"\262\330\266\277\317\332\230\254\300w\315?\233L\224\347/\275\240?^\245{L60\300\277B\340S\313&b\300\277\027Dh\333\214\376\274?\0033\n&b\204\243\277\357\245\217\310V\376\207?\262\301>\036\001\320\267\2772\017F\252e\213M?\251\037\035?\361\'U\277\252p1\220F\344\234\277\037\317\204}\333\301\265?wr\005\231\311?\311\277\307\226+\"C\246\200?F\034b\372WV\300\277M\1778\365\306\035\267\277v\232\031\005\273\326\276\277^\177\033rd\242\247?j\3239\2676v\305?e\365\334\314i\237\254?f\004\266\016\262\241\256\277\263\312\270\000\214\321\266\277\220\343\035\236\336\325\302?m\253d\231.\265\251?B\340%^>\305\235\277d:\363[\004\t\305?\035\001h\246&\213\256?\307a8\304f\233\270?8\344{M\323B\312\277\250\007\366\203y\332\240?\033\270t\376.lz\277e6\241y\\I\272\277dP2o\206p\274?##\257\262\200\252\261?=`\375\020L\233\302?t\313\373\003\266\035\274\277+\'Sd\347z\307?\005\332\321\225O4\242\277\367{\302\312}@\263?\3525\300K\3232\301\277\342^\207?u4\223\277:3\334$f\300\230?\033\3557\315:\232\320?\217%j\2367V\263\2771\211\240\035\023\314\260\277\026=S\350\354\315\236?\211\327\345\316\334\026\273\277\201i9\030\367`\223?\311\320\000\236s\235\301?g \224W\300\033\320?~\251\236j\3261\261?5\"\206qQ\262\246?\t\310\002\232\327\311\274?\227\353M\307TN\275\277\336M\223\360\345\250\277\277\222v={Abv\277\350\237\003\020\006\r\307\277\201\213\336\004\tT\246?\374#L\207\265r\243?c(\371\271\263I\267?\020\002mJm\010\272?\340\n\314\313\354ev\277\247ZQ*k\210\322?/W\330\310c\327\272\277L,\253\374\376G\306\277#\245j\314R\333\272\277F\274\223\254\'\344\303\277\351\305z\374\315\013\273?\013\332\274<c\226z?\004\345\331\3508N\260\277\215\222\362G\322 \270?z^&+\014l\242\277\302\031\314I\365\277\303\277\327\000$\275a\302\321?r\177\036`\361?\314?\272A\322\032\035F\240\277Y\010\326\306\274\365\252?\343\371\241\220\214\005\263?<=\272\2325\206\300\277\367=x\020q{\303?\306\312\037`w\306\257\277\334\314E(\206\352\257?L\2531l\245\220\251?k\222\352\233\2240u?%\030\257\327\371\223\241\277SC\340\262\345\252\222\277\177\001\352{\234\237\241\277`\315\226Q\320\252\301\277\344\257\224\235\031R\266\277zg\2545\2673\214?\330\306\177\250v\262\267\277\217g\260\355}P\264?\314\274\014C\330\374\213?\021\034\242]\022\221\305\277\354x5V\364a\274?\253|\202O\315\245\240?\366<\230\330\352e\245?8[\214\222SD\305?\332\334H\372b\273\302?\260\255\243\352u/\230\2774\240\255\"\245z\316\277G\323\305\203\300\364\216?}:\300\226\266\023\301?i\316\277\245\022\016\241?_*\007\250\n\034\265\277j\0215\351Z\373\306\277\206N\006X\366\362\272?\214\220\276\220\023D\262?\317\241\255\0013\344\234\277#\017\003\374\\0\214\277\310\214`\201\342h\250\277T\241Q\3325\270\260\277\251U\322V=\255\237\277d\366\340\372\002\271\303\277\213\231\017\263:\206\227?^y\004\263\320\257\263?R9\027\"~\241\317\277\360K\240\251\005i\251\277B\374BD\004=\237?c\345/\361D\302\260?\326\240\271q\325\245\301\277\177\257\312\025JPZ\277~I\257\255\274\270\271\277\036{\323\265\005\240\304?\014a\215jE\234\221?N \255L\335\001\252\277\020\255\304`\225\315\324?\002_5\326.)\256\277\r\206\326\253y\344x\277\276\305\007\234]\316\301\277\272\314\223R5\223\266\277]\337\354SX\250\216\277\"c\202\222\033\237x?\021\315\211\027\032g\261?\026\307\331\362\006\215\272?\376~0\'\263\344\315\277$\321% \2237\257?_;\031QO\"\247\277uw\361`\376c\244\277\322\226\251M\341\333\240?ad\000lP\334\307\277\037\0029Iv\351\252\277P\202I.\201\321\241?\003#\276a\260@\321?\203\315E\3654\002\303\277\372\002F\307_\372{\277\024\017\025\251]\217\207?M\010&\233\250O\303\277q\223\267\177PsZ\277\353,(_\232H\275?\026\022\207n\337\262\311?\3779;K\260v\301?\323#v\237\225\376\273\277\261\320\210\313^?\266?\303\020\353\372:u\313\277\314\333\300\316\364O\275\277P\214\347\t\001\212\244?\345\266r\232\222\020\267\277\375\305iE\033\275\263\2778\277c\363r\201\223\277\3141\335\312\260\340\252\277\370\344P\003\\\202e?a\327\263\371\340\261\256\277\021\315R\335,&\237?\365\353k\203\234P\265?\013\0242\310\020\274\320\277h\240\300\217\261\352\221?l-lM\243\267\203?\201\265\245%#\336\260\277!.tuc\267\255?\323\303\367\312\274\376\261?\263\214@\320\270b\274?S\215pe\006\370V\277\0057#\364p\313\246\277\'e\347\024M[\214\277w\272s\'@J\305\277\201E\256\363B\220\234\277_N\322\322\241\345?\277\241\315\233\232I\241\303?\311\326\326\374\232\321\300\277\272\026\206B\323u}?\327R\273AE\342\267\277a\310&\376V\354\305?\213\251p\343%-\204?\210\325\177\034m0\250\277\315\321\312(v\317\247?\207\237c\263\230g\323\277\r\032D\302\035\361z?\332\332Y\313\364\031\246?\236\212\355e\237I\264?\t\307\310\272`\020{?\372\225\330\206\251\302\300?\365\342=\261\346\201\272?\313\255\007\014\254\342\210?}\246\355\225]\351\274\277\353\0341+x\323\224?\343<\264(Y\373\260\277wO\004\362\213\376\222?p\017J\211\224\006\233?\366\355?\317\3065\265\277\327&\253X\277\344\206\277\016\333\002r+<\177\2774]\215\213\3675\267?.\302\245RX\305\311\277\005\200\220\261\017\203\263\277T\324\354\025V,\266?\032\234\325^\267\330q\277\177\367\316z\322\025\245?\202\353=n\355\345\260?\\\342\255\'\223^\246?\230T\337\273\260\271\303?\021\262\272o\250\027\217?\030d\220\216&\266\275\277\2325\212.n\266\317\277o\254V\201\337\340\255?\355{E6\245\230j?\007\327\242D\373\332\263?\337\254s\023v@\216\277N\227B\263C\323\276\277\303V\256x\277\337\310\277\335\216\351_b\010\265?C\363\277\3715\016\260\2777.\204\355\212\320\267\277\235\302\027\246\353\321\303?5\224\234\241sW\262?s\3247\201\001\"\275\277\320\361\210\347\240\340\255\277\333\t\200\241f\310\325\277\240>\000;\2523\253\277r\232\261\261,\262`\277(q<\n6\337\261\277\313x\035\3614\034\302?\215@1\266~\023\252?\2752\344\270\364\260\277?\3458l\263,\241\230?\377\006\006\3122O\231?A\t\245u\366\246\237\277\337\334}lR5\305\277\341\266\371\223\312\265\250?\340x:>w,\236?\246\304qF\230X\301?n\262*\206+\345\250?m\207\311\232F\360\245?\256\235!\202\307\275p?\355\036;\037\210\263\300\277\334\261\374\"\213^\311\277\335\346\335I\212\333\304?\222\353\311\242\333\037\262\277u\341\275\350\327\305\231\277\027\"\236$\036\005\240\277\322h\016\207O,\214?\266\274\322J\255!|\277\240\262\032\25559\274\277I\311\000\362\350\357\306?V\035\315\316\211\177\303?\374\036su\r,\275?\360\024;\003S\007\263?\022@\251\2378!\247\277\204\234@\313\330z\306\277iq\345\2128J\311\277V~\330\037\037(\311?:\'\212%U\234\274\277\275\235]\353\\\222\251\277\2421\274\250\304\344\310?7\222\333\322w\266\223?\263a\005e\372-\215?~\312\252k\365\306\267?\214\211\013\235\376\217\254\277\252\177\313\273?\220\226?\252\265\223\377\031|\272?\317\337\207\364\254\215\256?\342\002\232\321\026\270\273\277Hk,\335\006\253\262?i%S\310\'\034\262\277!s\270\342\237\210\251?\224\363.V!I\300?\237\215\340\350E\222~?\3175\033\324\273(\334\277\376h\024@\'\271\261\277\240\261\330\363\224\354\254\2770\022 }oJ\262\277 $\371]\363\372\243?\214\037\260\367\253x\272?\017f:t\326\025\266\277\240O\340j\223o\320?+\212\327\315\216\307\226?\024\276\230\034\371N\257\277\022\205V\033\252\037\217\277\321\206a\367\242\300\317\277\013Q\241\341D\r\241\277?\215\364\003)\334\277\277\254\205\377\322\360\227\267?;\347\253\303T\017\271\277K\177mU\037\337\270\277$\034&\214\002\037\243\277CGnW\007\375\270\277\262`>\322\272\350\262?\333\312\241%h-\314\277\263\276\217O\005\300\306?\211\347\315\325\242]\215\277\277\353\323\353G\212\240\277\277\242\0335`\363\311\277[7\304\013b\244\233?\224\354u\355\033\205l?\330\215{\3152\233\266\277\256\004\275>\350\033\310?R\034\266e\tf\276?\317\211\275\232\206\323\253\277\3746H\242Y\017\315?\'$\372\026V(\241\277Q#$\345\036a\271\277\307\024n\242\234\273\315\277\317\202\0004E\212\306\277\030\001\260@\334N\242?V\'\013>\366\254\210\277\207\265\325G\315>\313?,#\014A@\327\315?\332{S\035\335\227\202\277+>E\313~-\302\277\203\324yv2\021\250?\264\374\333\305\267%\251\277ilAe\336\264\263\277\033S+\356\033\216\277?]z)\203\233\321\275?\306\344\363\024\310\252\273?\376\3634\002+\313\306\277\212d\320DB`\275\277\2764\017h\355?\227\277\340\227p9\246\352r?@\260\332\276\271&\272?\300/x>\030\n\305\277\3508\346$\023\272\264?\364b\271S\332\006\263\277R\025\313I,\023\317\277\375\252\031\365?\275\204\277\214\033\037\372\371\013\262?\224w\226\3751C\315?\237DM\n\205\271\303\277\346Q\010\376\347+\232\277\033\2076\323L\271\243?\352_2\355u\013\230\277\334PU\034\377\274\243\277YS\226y\307\266\255?\342\341\231\rg\370\245\277\273\332\325\215$|\201?Jf-u\250,\260\277\206+\301\325V\332\243?\212\007\255\013\343\320\262?\261kBn\351|\264?j\337\007\354\241e\262\277\341Xp\347`\262\243?\033\03329\203\014\221?Htxn\202\223\276\277\003\240\253\2515\334~?\3628\234\242\224\233\254\277\3462\223\377>N\305?i\365\035\007\301\230\265?\014\204-L]v\224?U\203\324\245\362J\206\277\306/\240\312AF\212?\000\272\375\320\241\010\271\277\275\032\304\232\302\315\321\277\246A\010\373\306\035\262\277\241\245\307A\235H\262\277I\372\317L\277\002\265\277Nh\200,\023\265\266?\212\362\"\373c\261\312?\267\272m`\362\367\312\277\177\335\260\371j\346\305\277\3516I\265\312\363\221\277\322\375\010\201\014\211\313\277x\243\2019\020\274\311?\340\360\202\276>\241\252\277\2117C\277:w\305\277B&\252\\\205\247y\277\371\310\261|\344N\243?\260\220\246\001\251[\261?\357\375\267\345U7\257?\262r\010\rH`\301?7\014\201l\351\223\264?\367\246\363x\347@\247\277\224\023\242\273@\225j?[\036\316\365\236C\306?\256\322\214\236\223\201\264\277\365hD/W\304\242\277\261|\272\274\341\375\257\277\355QX\272C2\254\277\377\217\r\314\306\334d?\007\226^\230%I\225\277|\036\357+\034w\311?N\207\355\213r\030\274\277\226\274\025\327\263u\224\277\324B2~V\206\301\277\272\334\203D\235\255\307\277\262\204\206\374*\002\267?\202\036~~\341Q`?\302>@\267\365\267\231?lZn#9\370\250\277\2627\236\336\243B\276\277\271\002y\267\305\266\255\277 j\006HY\367\247\277\003s\026\313\036\357\234\277\323\323\t\277b.\276\277\034?>\232kk\322?\271\311y\233\373\005\207?5qm\275~\361\254?vj\000\016\331_\223?S\351(\037\230y;\277\262M\026x\276\177\306\277\223\375\343\217\004\310\276\277\367Nz\336\001\026\310\277\004\274\212\256\"w\225\277\177\277\365\354\251A\264?Z\r 8\377\311\227?1\227n\206\031\013u\277\221\247y7G\253\303?\213\215\007ib.\215?)\206A V\246\203\277Q\177_vuN\260?=\360.\t\233p\317?\254>\343\316k\224\270?\026\231\243\220\322\260\263\277\203\266\375\273 \373\305\277*7\265F\360>`?(\221\214=\255\276{\277\301[\322\323\237\224\232?)\376*r\0323Q?\217\261\230d\366<\304?\223\006\201\373\216\252\300?\026\005\017B \346\252\277\035R\252\r\204\227\273\277\231hDE\337\320\265?\313\272X\332\001\264f\277\327\216\004\212\201\205\250\277X}M\325\023\033\257?\333\013\272\007\301z\265\277/\014\357f3\304\272?\252\267f\321sV\311\277XY~\250\205\005\322\277\211\242\005\260\213\262\246\277J\247y\nGP\204?\310!\n\2624k\310?\221\330\272\217 \301\236\277\320\347\256\313\314\200\246\277C!\035S\232`\301\277\313\035p\026\256\253\275\277-\\\r\024\350`\212?X\314\361\352\242\346\314\277\034\202yEL\312\220\277\353\223\227\313b\334\222?FV\217\222x\032\267?z8\217\330\253\366\251\277\301\t\030\010\333\255\275?\363?b\364\341\244\243\277\203\3117<\214\333\240?,\274,-\201P\254\277|oA/75\266\277A{\215V\235\030\255\277)\014\026p\222\256\272?\340\013\336\240\003\237\304\277\250\227N\350:\342\225\277;\222\332\246\335\266\274?n\342N\006\014V\267?w\232\255~\031\202\211?\220}\022\275\rz\201?\267\000`\373\313V\251\277E\321CC[|\276\277A,%\360f/\202\277\022\330\236A?\031\276?\311\"\216\010\301=\306\277\004\230\240\001\356t\266?\\#4\254\027\315\254\277 \017\205.8\007\234?v\305\037\317\312\021\320\277\277p\314\255\224\304\267?\022\311\354L\360\243\274\277n\3349\247Z\227\301\277)\214h\334m>\240\277\005H\307/\217\246n?\353\231\000\315\024\254\302\277\306\025_\263\037*\320?aN\252\245{\336\256?\372\260\205\261R\253\222?\r\276\256|\314\276\245?\0059\257\346\027\350\263\277\334\016z\201(\350\200\277\023\310A\177\366\002\241\277\033\323\003\037V\206k?j\312S\234RA\244?\317\rH\264\271\030\253?ZO\020F\262\301\253\277d\036\302Jv@\271\2779Du\242&\035{?m\261\315\310\026N\316?\337\341\025d\224q\304\277\320m\342\377\233\002\216?\202\217\340lP\225\277\277\210\261\233?{}\263?\255\273\034z\213q\303?Y\354P\343p\266\261?\037\202wJy\302\227\277:\232\002\364\233o\307?\305\247\276\000\276\212\300\277\352\265>\032*\202\253?\3740\013r\331#\222\277\026p\002\232\006\n\265\277d%3\271\273\017\243\277\3269Hd\372f\264?\223\010\366ux\235\302?|\352\213\311\261\275\255\277\331\207\247\211\t\210\274\277\3216!\314Y\346\220?\220\203\365\253\241.\221\277\310\014\024\360\032I\300?NC{\376L\255\243\277\275\302q\275\304\276\306\277\263\301\364\357\243\232\216?\322\3732\264\3332Z\277O\314 \322\317\007\225?\"]l\177\024\331\313?S\314\320T\355\217\247?@\250?|\317\024\260?0x[X.\030\311\277\003\2515\257\005M\246?\257\265\017\236\036D\300\277\342\256\201\203\256\317\314?\335+\216\211\203\244\264? \333\261\321p\002\240?\324\307\327r\265M\240\277\217\016\017Q\032)\256\277l\333\316\355\370\233\300?B\336;\242N\346\247\277rs\313\253.k\261\277\241\252\266\017!f\261\277\337\376\353\t\362\206\242\277k\372b\353\372\254\314?K?\0161\035m\325\277]\353f\370\255\250k\277\324\034\300+\"\346\246?[\311e\t\265m\275\277\253\261o\346\2729\265?\032\250\0263\003\034\251\277q>\014W\010h\200\277\245\032>\031i\242\265?\000n>9\355\320\246?~\324\225\251\214d\272?\262#\006!\010\223\303\277\245\340\322&\251\033\263?#0\217\3352\350\221\277\260\326\373\253\177;\325\277\237\377&n~{R\277|\010\224g\357T\256?@<\260q\355\361\250\277\266HG\256HI\232?k\232\355i\227\211\320\277\236\360BZ\257i\277?\325\327;\031\377\240\200\277\244\033(\343&\214\312\277\271i\236\361\346\324\272?\\!\005\322E\003\265\277m\\\242\027\201j\260?x\342-\343q%\230\277\205\003[f!_\273\277\374\363\262[\365\216\262\277\302u\222\003\257\214\250?\365\366&\333 \373\272?WUH\2503\242\245?\007\327\025G\366{\300?}\303\\\347~\261\212\277\033\246\270\201\340+\275?\\\233\306x\245\315g\277\200\340\216\345\355\350\261\277\232#\202/\340n\237?09\245\\\0174\245?/\0130\3619[\266\277\373\316#e\363\363\225\277\255\327\254\271G\312\260?\252\356\356k\263+\260?Op\240\206_\225\305?\305|\332Cz\033\223\277(GC\3061\257-\277\207\301\213\006QA\304\2779\r\031*n\336\244?\3670_\0215K\244?Q9?\000H\272\253\277\313\272\214\211A\221\272\277\014\032\263\224\262({\277\325D\355)H!\246?\367n\262~=\221\277?\363W\341\302v>\254\277\0359\312i;\303\201\277n\177\270|\337\022\262?:)\323\252\356 n?B~\303\3408\365\264?\240\036{\323\370\214\244\277\226(74\270Q\313\277;\0060\316r\242\260\277\362V\366\254\375\214\300\277\230\372~\036\377r\267\277\302p\255\0109;\273?M\000\253\215k\316\233?\245c\242\246\022\037\244?\315E^\037p\307\323?\326\252l\250\000\270\264?i7\373\331RJ\251?\024R\035,\tq\301\277Y\347\001\301\245\004\267\277\201\327y\204l\375z\277\271\236\272\235\366(\304\2778Z\377\016x\371\202\277\260m\333\257\226\212\264\277\323\263\377\205\217\255\221?W\271\215$\214F\252\277\t\377\263\302\263\177\321?\306,\242\257\300\007\276\277\256\302\315,\233Y\254\277N\332\350\006\tt\324\277\036\302f\254\t\306\235\277\375&\007\355\231\013\306?\273\377\355\251\002\225\302\277k\324o\024L\250\245?\321\340\300\034\026\027\221\277\260Y\007kA\213\265?\352\001\215\242\343x\211?z$\032C\230\231\302\277\022\004\024\2229I)\277\257Tg\356\371\346\263\277W\212-\230\372M\320?s\271\255\307;\340\241\277\341\224\341\333B\312\306?\213-\272\005\245\200\230\277?\367\302z\001$\300\277w\023lEFt\232?\357\022h\347\t\231\266?\001\203lS\351\217\277?\345\276\274\235X\003\207\277\315\026\237\364:Q\220?\376\363\263\211\341\345\307?a\021}\337\331\272\304\2773\247-\354\215\311\255\277\003gD_\014\302\247\277\210\001\307\005t\241\255?P\223\341&\241(\220\277< `B\034\222\260\277\224\261\306\212\333\344\273?\311A\313\336u\234\260?8\315\235%\214\233\237?d\274\330\226\334R\270\277m9\301W\326\362\267\277g\302\3574\2760\270\277\227,\264.\3574\310?Q\037\333\364\274C\242\277\317~\317\032-\214\215\277\002>\001\217\373#\221?U\205\010\227\376\360x?\232r\362\203\024\007W?\370Y\246\355\332\346\216?~\265\243\000\227\227\305\277\325\226\266\206\223\364\306\277\313\006\nwXD\274?\250*M\250^hy?^e\276\266\273\235\245?\262\267f\265\312(\267\277\033\374#x\364\010\254\277<t\360\014G\037\264\277+r\362ke\202\241?\274\277\273@\371\201\311\277\330G\306\226\203a\267?\340\3765\244\210M\273\277\310J\3714f\205\267\277\225_\333\311v(\215?\356\362{&\267H\261?\302\257\241\310\241p\200\277\310)\361MK\267\270\277Y\267Z:\207@\302?\345A$\274\352\213\236?\336\'\000Q\326\021\321\277\352{J\207\017\322\231\277\356\241\344G,#\273?\201i\353\3255/\215?\303T\335K_\023{?\260|\336\330\354\303r?\003\342l\214~\004\225\277*\344\221\260\205\303\305?\3043\236\227\035u\203?\to\370\350\035\333\207\277^s\2117\202\033\177?#\260\245\356i8\261?\246\346\225\177W\321\301\277\010r\253\210\306i\320\277\032B\250(\353t\212?@<\302\243\354t\257?P\226L\312\274?\300?]\270An`\002\330?\311\262\276NJE\202\277\177Wvwa\273\272?\032\275&\214sp\247\277kS\316\177\214&\303\277r~{vi\324\240?\301n\215\316%X\212?}%\243+\352\227\303\277\347]\334\n\"\220\251?\317%\003Z\243\t\321?y\317\"\352\264-\263?\272\007;w- \327\277\305)\255\303s\230\263\277\022\330-\335\323\025\223\277\032\277I~\026\323\203\277\017bN\323\323M\217?\336@\027\222\227\371\266?\350w\316&F\342\261\277\245\303re\016\215\242?\345a\233\031R\200\231\277?N\257\304\341\346\272?\236V^<\225l\262?\306_\267O\004b\226\277>\231\320\220\360\273\227?CN\375 T\223\234\2771\016\371\337ci\271\277\022\337E\351\235p\232?\215uw\370Gp\246\277\302\247\221\363\327\321\272\277}\"\267\320f\017w?\036` d\303z\276\277\006\320h\267\014!\252\277\326\272`\025\034w\301\277m\302%\022\363\021\270?\374\214\332\324B\302\260?3q\213t\224I}?\302\256\266\244\260\263\263?\267\034\234CW%\226?u=\353\3746\014\261?\242\212\3408\351\216\304\277LtD\265\247N\220?K\340\211\031})\253?\353\225\241\253\n\"\252?\325G\245\373\030\223\303?\321\255IaM\307\301?<x\002^k\022\274?\272\016\207\311\251]\201\277\346\357q\032k\251\261?Yi\251\231\035\'\271\277n\263\217e\203i\223\2772\303Ve\347\002\242\277\372iO\270J\202\233?(\267%\225\311T\253\277\'\276{\224\202|\272?v\220\214Mt3\251?_:!\314r\273\205?3C.\321\305\037\266?\rP\360\321\307\325\306\277\226\021\217;\314\362_?\213^#\232MO\305?\262\227\250\374\322\334\024\277\274\207\357Q\317\271\255?\364\232]\256\325y\262?\223\320\276]\256\271\252?F^1\335\031e\226\277\333v\320\364\035\304\261?\332\246\334\014\203S\204\277\372N\177\356Q\326\305\277\324)\240L\212u\303\277|\341\331\025Bx\246?\374\025\215h.\257T?\024\255\036\202\342\324\245\277\245\306\374\207mV\256?.\226\362[\202r\265\2770\250\266:\201j\306?\246\232\345\343\304\353\301\277\335\2604\314\032\207\226\277\275\342a8\036\n\320\277\273\231\351f\372\367\303\277\336\3036\243\333Vk?\202\273\254mg\343\304?\202\224\334i2L\261\277\306@\025\332\203\240\224\277\346e\304QJ\316\255?\355?[\026*\333\262\277\\\370\335\305\377\325\261?<y\345f\262\\\270\277\263!w\263\273_\235?UD\355\022\354\\\315?9Iv\226\243\036\244?\221d5\372}X\255?\345\362\220\247<\331\302\277\201\r\210$\334\357\244\277\271\235\340%\'\003\263?\341\347\312\035s\274\270?)K|?{\000\267\277\034x^\321\276\371\200?\260Q\023A\031&\304\277\273\351\307\316V\270\267?!\n\374\026\026\276\300\277\233\305\177\267 T^?\322>\303X\305\001\262?-\223\230\n\376\244\303\277\355\033\330\275K\224\231\277\2416`\3763\000\303\277\365g\373\310>\250\304?\027\326@\360\371\222\252\277\274\371\321lY\303\301\277/\261\025\317\274\251\270\277\360\222%z\017\036\243?\336\277\004\270,h\264?\034\255\361k\023J\217\277\267\201\272\323\362\216\305?\324\005\332\004\251\210\263?\324\204;\230Py\270?\337\251\375\323\360F\257\277O\3237\322Q\024\231?\276U\266\327\266\244\267?\022\271\325X\2503\311\277\231\331\243\367\0223\271?\227\343\262\333[\017\245\277\275\3639\007}\'\303\277Q\206\240\321\005\274\240?\300\223\261\245\315\333\320?\367\024*V\034\263\215\2776\2123\356I2\302?\264~\2550\201\325\320?\324\341\001\367s\316\261?\260\no\234\227S1\277\252n[-\217\325\276?\265\014t2\300\346\271?z\201\2734[\273\237?\346T\304\0219\377\250\277\263\272\257:J\263\254?\3061\201\261-1\241\277\021\322\370\276kQ\304\277?#\010R:}\315?H\235\304w\013C\323\277\225+\330_\363\252\246\277T\033\262\301u\345\255?\371\243\345$\370\224\303?C\327\225_\367_\271\277\"\261\354\374<\313\250?\025\262\256Z\250V\247?\377W\024o\223E\273\277z\363\361\003\367\245\207?\211$\333\257QJ\276?E3\"h\342\003\233?\007\000\370\202\244\273\306?\0264\026\327)\310\200\277D\234\033\347\325\032\320?\037B\341\033\2226\315\277\020\373\260\306\205\210\244\277Q \353y\305\202\217\277\274\343t\300%u\322\277Pn\213e\'\260\237\277V\202l\333a\312\250?\004j\024\315EH\226?\n\016\002~\205I\244\277x\333S\375\367z\240\277\270d\t\276\006\277\200?$\341\005\322\273Wt\277{\3164\242O\245\243?K\225c\3106\206\260?r\nc[\204+\300\277)\351\001{\375-\256?\037\271\314Kxy\244?\216@\374\201W\001\266?\035\024\201\276\212\313\300\277\000k\337i\225A\306?b\302\022F\221\351\267?&\207\303\214\362K\313\277\002h\033\261\230\347\302?LB\251l8\255\221?\200\245\266u\346\205t\277O\351\257ho\231\265\277\374\215\177.\363Z\260\277\272\2647\260@\317\236?X\304\275-\250D\275\277C8:I\233\305\261?\\K\331\005{\202\272?d\216\331\336\324\177\310?\322E\334Z\235\354\277?\367\216\252\3175\305\243\277\351\277\247\217\002\251\273\277\265\215\362\267\034I\200\277\254-\235\355\233\300\273?8n4|\023\326\215?\330T\"\016\333k\266?z\341\370y\307N\327\277ni\372\334\025\211\277\277\2458\372\337\311A\243?\331\365s\334\276\320\265\277\022\321[\0061\236\265\277A:lT-\250\200\2774\273Y\251L-\245?\364))\272\360\003\235?\0331\341\275C{\266?\331\325\277>e\000\274\277\302\000\255\030\310,_\277\344\223bh\370U\207?~\200\217\303\323V\277\277\035\252*\323\017\244\222\277v)\252HV6\326\277\303\211XpxB\250?\342\346\236\021\013\214u\277Q\357W4:\231\240?\241\263>ye\226\270\277\273O\305 &\356\243?\270I\326mV\026\223\277\315e\363m9S\275?\022U\035\377B@\264\277\200\024\250\345\262\r\240?\204o\223\245%\234\303\277\006R{YB\314\306\2770\217\273\034B\246\222\277W\345JW\272\207\210\277V\356]$\341B\274\277\344P\344e\'\310B\277TmY\014\270B\240?\240S\354\323\332C\242?\2168F;\357\363\240?a\347\355\375\022s\231\277\r:\307D\314\200\243\277\351g\267}\021\377\301\277\303\220\343&.\232\320?u\031h\244\th\247?\t\351H\003\004h\255?\315\206\337\221\357X\271?Q^OyZ\331\263?,\036\030a\364J\207\277\335\001d\362 \315\217\277\017C0\246\363\367\255?\305\020 \260\231O\242\277{\236\265\230\235\272u?(`\244M\361\370\275\277\265\014o{l\275\220?-\231\314\331\036A\260\277\t\306\366\3106\006\211\2772\335\223:8%\201?\343\323\017cn0\272?\000y\233\036\347\334\300\277N\001\200<l8\314\277\201b\265\253\305\233\262?\177\372\366\365\326\211l\277\323\036Q\"s^\263?\016t\353\210\023l\266?\247\236\220\256\233[\234?\326~\306\226&\260\302?\341=7\374\351Y\262\277zg\310\263\3368\215?\304\226\205\0077\232\302\277\313\2010\017>\222\212\277!\227\211\300Y\370\221?\376s\207\226i0\253\277`\315\225_\324\323\267?)br\224\201\372\227?\325R\250+\220@\262\277\241\371\020M\276`\244\277\353\323;$\272b\304?\022Fky\271\274\314?\372\315\007\234\3452\300\277\247\215r\237k?\322?\265]*~\220{\265\277KB\227r\037\226\263\277\020\263\320\274zG\274\277#\211\334\221\2120\320?\214\267\030\274E\030\314\277Jm\022\306\030\274\313\277\367\273\375\350\210\014\236?\370\224\346\2116\234\250?\006\362eY\2343\251\277p8\2776:c\221?m\246\000\343\357\252\311?\257g\'\037\264\236\214?\351\333\2562\253x\214?\217\245m\374\314\373\234\277\325k\271p\253\013\253\2778\235\321\337}T\265\277\025aY\021\335\000\301?\335q\302\3262P\264?gagQ\243\231\210\277\247\367\3478\347\263\242\277`\311\3705_\204\210?\203\274\341\010\273n\264?\244\247f\300X\235\304\277\362r\207\223t\225\274\277\216P\206\253\256\033\277\277%\257\302@\037\020\277\277+\220\025\014\007\317\222\277]\217\274\340H}\301?\363\017\272E\3256\327?\006TB0\220Y\300\277\210\310\253\275\203\211`\2776\302\257\000O\200\243?\201\230\274<\2165\262\277\276\365\335\213\355\241{?\242\3306\030.\214\271\277[\366\352\013\'P\214\277\001\237&\345/\326\273?lJ^\014\202\014\235?\216!\253\217\0042\311?\361\337\206\224\235\343\274?\214\177\030J2>\221?{}\262?A\010\207?\'\024\005N#6\243?\325\001\316\267\356\234\266\277\007j\020\313\003S\312\277\240/\014\205\352f\235\277\032qKrqL\270\277\331\033\2041\355A\302?\3064\260M\332\246\265?\232/\2472-\310\306\277\353\002\314\rR\333\231?\rK_\220\031\234\266\277\244\233\260\3210\265\177\277\337|s\362\334>\302?\261\317\356i\231Qr?\361\217q\022-\366\255\2773*\322\027\270&\301?\211\254\360]g3\300?a}\341\342#\343\245\277\340\215-\305\212\032\225\277{=3\361\265\361\302?\013\236daTm\216?\327\024N\360\345\275\227\277r\347\033]\324\027\244?O\370\037k+\244\234\277\341R\371\225\006\223\230\277nK\022)=X\246\277\277]\234\204\013\235\305\277\357\233\2530\337\274\256?D\250h\310vH\306\277tV\207\352\024\320\263?\266I$\364\2450\232?\177-.8\220u\307?Q\230p\030\342\333\245\277\232\330A\232\347\034\301?\034\275\017\233V\205\300\277A\017h\357\037\022\271\277S\005s\343\nP\265\277S\006^\234-\021\307\277|uG\265\226\301\302?\367\r\026\243\360\210\323\277y\261\375\016[\237\300\277\365\010\005\371\332\263\222\277{cUc\3743\304?;\216e\363(#\324?c9`\204\377Z\275?c\203JS\033W\240?<j\210\227\271\307\272?\2407\002+\230\031\273\2775o\325\344ur\300\277\340La\275Ro\220\277\375\371\300\306\220\027\303?\350\035\366sC\252\262\277\224\312\333E\206\344j?\031f`\2474\\\266\277\307N\224)!\241\313\277\314\243\357\345\365\355\305?\336\320\337G\207b\311?\331UNV\247\345\243\277\303D%#rm\275\277[.D\227O\217\277\277H\033\323;)\254\312\277!_-\372I\\\246\277Lf\211\310\331\305\266\277\024\037[\261\000\235\242\277R\324\341\2426P\274?\301\\d\263h\243\240?|\333z\203\222\275\237?\3574?\370\330\211\266?\302p\316+\365\030\242?\025\264\272\256_\202\272\277\210\310\222\377K\371\315?x\362\236\345C1\302\277\016\322\224y\355\245e?#|\201F\374\331\255?S~\"\2466\242e?\370\374\252\034\006\330\250?\237D\226\025,)r\277K\316g7\232\376\225?\021,0?\231~\305?\205_9>\235\177\241?N\376\335\331\345\037\230\277F\237@z5\312\320\277\230.\305\271\007\007\271\277\342\252$\205)\033\234\277m9\006s\222\245\213\277\213\345\206\347\376\215\266?{W\377\270\304\036\251?/ZT\024>\330\202?%\365\362\243\345\364\303?U*\033\371\224g\250?\013\263\320\200\337\323\275?O\345(d\254\366\273\277\030\016>\347\360r\270\277\265s\367\246\362\330\241?P\276$\374\303\225\266\277\257[\3510\361\363\276?u\344\246\325\277\262\206\277>\325==-\345\220\2778\017\377lAH\300\277\244\262\246\276\330\000\315?\302\242i\244t\272\235?\273_\234T\250\317\266?\321\253\352\020\242(\233?\212|~4`\232N?5z\367})r\254\277a\256\200\266\314Qt\277\224y\210\244\r\336\262\277\226\315\300o\205\260\205\277\321\210\233E0.\307?\341\010X\213\001\005\200\277\013\200Sy]\341\253?S\007\273S\002\014I?\277\273\267\357h0\301?iN\221-\016\341\314?c\252\271Y\277\177\216\2779\341D\366\366\024\211\277\347\241M,\377\356\301\277\2201\315\274\300\327\300?\3079dv\211\352\202\277\245\021\263\342\0258\245\277\245/\371uC\321\306?\032\331\232(\344^\225\277\003P\301\301\354\364\231\277k-\242\343\237\374\236\277:~\224t\367h\225?\037\207*\031\256\023\244?\252\013\251|\265&\301\277\245\377.st\232\302?\036\335\343\r\335\252\252?k\224#\365\200\277\244\277\331\355\301\025d\350\313?3\206\272{\303\235\206?<\242\013\217t,\241?\202\325\306\365\361\021\255\277\330\332z.\375\302\206\277\214\220p*\356.\265\277\264n\327\t\236\215\225\277\221F\032Y\321z\221?\300\337)(\'\370\247\277.\301\250\354\267\302e?\245\316k\373\324\217\310?\207K\200ub\034\325\277\'n\026\023\337\265\207\277aE\236DW\257\264\277A<@\251\310\211\263? w\244\2119m\255?\"\204iy\342\213\201\277\343O\247\353\270\265\211\277v\'4V\322-\240\277u6\246p\006\227\300\277m\032\256\362\354U\224?\246y\371\244\313\230\264?$\262\325\351\3345\306?\201\025\311\216\2573\222?\370\207\212F\206f\310\277\274\366\261\264\352{\263?d\263\3355\216\236\244\277d\036#^\302\330\216\277q4jh\005i\247\277R=>\351\027\035X\277\222.,^\256\303~?\237$\360S\310\242\270?-\303uf`\376\233?v)\3542\352\343\221?h\\\t\264y\246\260\277*\260\0237\366\231\271?3$q9\206q\274?QKK\371}\340\303?\312\307\321\237\337a\267\277\307\373S\214\234\366\274?\'\241r\213\360\341\264\277R\302\360\372\304>\226\277\246\320\206\302\247O\301\277\332\306\200:\320\326\301\277\371\005\371\363\026\037\311?\004\226B\003G\326\252?\241TX\021\344\206\321?\246\346\023\007v\215\245?\026\200U\312K\035\300?\222\344\341\007i\322\316\277\214\020W\215\325\342\250\277\032\023\347\373<\353\252\277\312\273\216\374h@\266?j1\253\246\016\266\305\277\203\024\303y\376\025\262?L\375\010\325{h\310\277\253\264#u\250\223\312?x\320\0363\3013\271\277\363\236\365\352X\032\243?\354a(\001\242\340\246\277\032\232\231\334\001s\300?W}\214\014\333?\262\277\267\261\377\014\246Ve?T\301T\"\330\234\237?d1A\376\341q\203\277+\277\027\231\2223\271?\313/\276}\";\257\277u\240M*5\267\331\277\371\\\365\340\311]\243?\334\014Kq\204\270\256\277\005\020G\026\326\031\300\277\252\250\030\303\226\372\265\277+bw\205\244Cg\277\303\200\3306`\267\225?\361\005\260~j\032\226\277k\322\301\264&\005\237?DW\372.z\374\217\277\361\320\030b\250OG\277= \315\221\206%\303?n\312\200\032VN\226\277&f\336&c\233\235\277\212\274\301\273\237\037\227?\036Q!\340\222\304o?\"9j[E\341\225\277n\362h\352\262\277\267\277\361\303M\013`\362\262?\363WH[\277:\267\277\271\354TK\231/\227?\334)\317\270\241a\270?\017v\3240\220\r\254?v\362pT@\277\240\277\341\205\310\373\230\370\220?\373\311Z\360\230u\256?\016\206TEoH\254\277\304\256\223$E\331\266?\302\206\203L\000]H?u\320@\010\016\343\246?i\362\235\270\tZ\272\277\345R\020\312\361\316^\277\255\266g\271\017\n\312?\340~R\227iQ\247\277\024\346\202\030\325\373\224?\251\364\230:\277\256x\2777\315g\332\326\361\270?\014\340\004\207j\347u\277\030Nm\254\333m\324\277\320d5\022S\026\242\277\322\351\017c\030\272\206\277\211H\nS>\310\307?\030\256\336\030\001\235\302?\364\252\211\324F\020\300\277\024\272\366b\t\254\306?\245\001+|\311\332\247\277\273\213O\237\006\013\267\277v\304\205g\214\325\211\277\326\254\031\340\216\236\240\277\246q\\j\n\275\273?y5\316;:\030\223?\355n[\333\305W\300?r8&\007\253:\245?\327\223\245\325\270\310\231\277\252\3133\031e\237\276?\246\332\324\"\244\327\242\277\323\237\254\343\246\274\324\277W\302\3038\'\314{?\301\355\271\366\273%\265\277\325\010>^\307a\255\277\025\t\223\242&\212\302?e\3758\2228\275\220\277A\332\3552\207\217\304?2n\242\022\267\261\322?\303jwV^\275\264\277\260\033\030\222\263\016\304?\014\263\211\"\351<\303\277^<\315/\234\266\273\277r\366N\n\377T\215?\316D\3164*\374\271\277\275B\303`\214nV?\354\345\232 \347\026\267?A\324\226%tO\252\277\273\325l\r\363!\264\277\030\356\322o\204E\304?I%Y\010\006\235\251?p\t\013):t\237\277\353m\340\266i/\312\277c\361Y\305\203?\235?r\003x\257\343\320\213\277\366K0\356\335\375\314\277\245\333\2531In\262?\036\003c\215\340,\234\277rYrR\216\266\301?\317\254\005\000\032Q\315?J\374i\312\334\326\275\277[\321|\237iA\247?p\361\353\345\nH\311?\345\332\033*\272%\313\277\322W\344\025D\353\227?\320+i\034\221$\247?\330\345\007q\373|\267\277\231\316b%p\320\253?\321\024\002\365\233\221\264?\'\377\000\356v\026\302\277j\t\267\271\257\014\264?\323\3122\361E\312\306?\207M\326\353G\221\270\277~\215\033\335@\217\312?9\300\307\374\303\362\305\277w\211\250%\221\020\266\277\335}\374z\275y\272\277\"\347y\275p\031\250\277x\335\027SX\002\247?0\273\034PN\211\313\277^\310\024\310\300\320\221\277w\227\240vD\002\224?C\201\322\224q\327\300\277t\016\2676\032\265\312?\256\002\212\355)o\273\277mDH\357\311N\220?9\346q\364#\277k?\026\361\366Bf\256\245?>\253\02253\221\262?\241\277`4\177\342\256?7x\356\271g\237\305??\314\032e\227\021\236?A\\\247\216T\270\237\277\223\245edf\314\251\277\2748-\330\354\010\256?Y\372WI/w\263\277\330:P\324\305u\271?l[&\033e\320\247?\267\024H\211\213U\262?\360{\001Q\332%\266\277iO?\023E\035t?Rx\017/\211\264\325?\231<\030\302W\014\264?\002\032\272J\366\'\307\277\257\265\325\035E\211\227?[G\261\032\2509\301\277\007\333.\364\201\013\214\277m\250J4l_\242\277\354\211\233*\324\037T?T\n\373\355]F\257\277\224Q!\223\376\347\276?\234\261\375\322\257\235\300?\326\372[\203\237]r\277nn\351\274\230\225\222?\267\323\305\266\377N\251\277w=5\274\005\254\253\277a1(\000\332\272b?b\347\315zj#\273?\0048\213H\243F\233\277G,\237U8\013\250\277\254\304\216\337\376!s\277\321\210\354\257e\023\253?n$>\361\201\272\307?SU~\232\264\353[\277\264\267\216\r\316\tr?\240W9\220D=\307\277\272\002\341\231\373G\252\277\374\0067\303\372g\273?\177\014\211Ex\025\251\277/\037{\362\370\211\272\277\236s\2021{U\255\277\262K\240\026Z4\235\277\021|\262\230<y\264\277h[\005X!\312\216?d\272\\\310!\211\260\277\203\004Nn\317\244\250?r\330\244\\\246\370\311\277tr\017Q\\a\271?E\030a\027\316\273\243?]v\316\216\246\374\315\277\177\r\276\3253\010\243?<\030\255\223\303\347\266\277\340\374\010\253\262\317\247?\322\327\201~\327\265\316\277=\n\307\252\241f\276?}~\205\026\334\223\261\277{\237u\304\202^\322?\271\274\313\013\213\206\300\277\267\017\331Nv&\300?\231\002\017\317ue\210\277\227\264\211\260\305*\301?(*V\230-c\253?\204\347\276\210\021\320\247\277\272JOS{@\305?\274\022&U\302\260\265?\324\335x@+\t\275\277\025[M\007\333\314\254?\270\306\376 *U\267\277\354\375\355E\360\223\216?\2311\357\306T\016\250\277\014\201\024\205\035\215\272?,g^\325\221\277\261\277,\227\207{\025\002\227\277\"\263\305\"\363\332\303\277\022aPp\351\365\304?\233?hd9\335\232\277\240p1\221R.\266?|\006\354Wx\265\273?z\375\017\342\034L\232\277\016\310T\342\203\374\223?O,/\236Hw\276\277\375V\205-3C\275\277\363\034\332\177\027\345D\277\000\200\323C2Q\260?\242\346N\r\253\213\270?\210\330\311V\315\204\303?\344J\263\376\232L\227\277~\340\342\"\"\221\231\277\214\004\202\"}<\312?v\230L\037\306\370\260?\233\217l\036J\033\234\277\006)\235\274\241Tu\277\272G\t7V\213\226?\375\240\3562\211C\272?T(\021\256\"\"\225\277\034\333\255\264\022`\273\277\315\332x\277\010\224\260?\236\234\r\224\320\312\242\277\322\372\010\276\222\273}?J.\030\237\327g\267\277\036@c\317\024\357\237?\246\351&\226\221\344\274?\035\025^\267\270\230\260\277\025TvC\031T\263\277\250\325\345#m\201\255\27791\007P\253\336\311\277Z\003\335hp\352\304?\026\254\272\212\366j\255\277o&\003]$\344\310?KK\372\331\243L\304?n\224jN2t\246?\340+J\373\203A\303?\276\r]\214\241i\265?\351-\215\201\023\243\261\277\006\302+?%e\250?\022\230.G=\264\261?\333@\202F\324Z\252\277\0075\013#:w\245?\341\263\027{\254\224\263\277\303\257\220\215\310\331\311\277\273*\030\325L_\305?S\233\215^\005\032\237?d0\331\246A,\303?\036%\321\264\237\241\252\277\271\265Tvi\274\247?7\010h\354\004\353\215\277\347\246\242\247 \017\235?l\001\332\356\273\354\260\277L\367\347P\311/\216?\362\251a\037K`\301\277\372\0231\301\333\264\304\277\351\353\227\363\ro\237\277S\264\000\'mM\301\277\341\317D\251\262\243\270?8\202\304\2117\024\272\277\225\223`P\347\221\277?(\264\3755Z\226\327\277\363\220\253\376\200\013\321?\346\334\275;G\205\264?t0,\240\276(\316\277\024\344\265\004\261\373\270?iX\371\"\362\371\253?1M\253MB\247\305\277\033.Af\261\221u\277h\r\2179\311\013\246?f\341Rf<\360q?\000x-\377\357\322\304\277\371\230\236\247\342\353\321?\261\nq\226\232\246\302?u\273\335 \371\221\216?\356\310\367wl\227\322\277\331\240\300g\325r\260\277EA\003\225\305\n\227?\364\322\351\354e-\300?\211a\261C.+\233\277\343\002\002\024B\367\274?(\031\004\244-\213\276?\2276\353\025\217r\263?\270\252\375\236\246\304\271?\014\'\357(\252s\304?\010=\0070\235\264\304?\014\355\212\364\273J\314\277\352:h3u\217\\?\033\216\005\263\334\251\301?x\213\352\340\261.\270?<\2129\024$\370\300\277^\257\204\363\321O\245?K\013\310\304J\266\252?(\302\376\340\023\310\320\277M\017\241uk\004\275\277\343\334K\005\345\345\241?\236\325\375;^:\254?\354@\2637\320\243E\277\302\352\010\247|.\262?Sx)\266\342\333\261?\357\336\306\033l\022\311?\263\204B4B|\302?\020\330\307\310\246\014\224\277L\3213\200\357L\242?1<\246\3364\t\323\277L\021X\242O\311\202?\032\220\351\211YR\255\277Y\245h\032\035\231\276\277-7\034\013\233l\303?\252\3708\307[W\262\277\273\245mrZ\326\225\277qRc\'=\360\274?l\327\322nm\'\235?\312\354\030\030\205\234\225\277[\316p\310\t*\266?\177Z!\024\270\020\223?\333\264\310A\177C\260?\274rM\362@\360\322\277\245\232\021\373\252\222\224?\214\226?3\234\310\231?T\tE\000\253u\310\277\265\233\244\003y\231\224\277\215\202]\272\256\353\277?{v\\\271\233@\270\2778\214(\330W\327\251\277\016/\3649W<t\277\225\250Ej\363]\220\2778g\230G6n\255\277\301h\002\263\001\363\265?\320\016_\275B;\260?K\234\230D7\227\273?\3749\216`\310r\212\2774V\265\341\232\016\240?\003)\252\373\255\264\261\277\311\360\363vVl\241\27789\000\215_\315\263\277\210\227U\213\322\230\323\277\233^\270\234\270\023\260?\240n\013\363\2279\251?\265\320\020\323%k\313\277\263\n\226FK\231\232\277\024\231\375\002Y\266\262?\361{\345*\373\037\251?\337\335\271\254a\005\266\277\271R\003UY,\274\2773K?\264\035\353\263\277\003\200\215\207\031\211\303\277V\374\342\362\246\376\260?~\222\314\374k\266\306?\212\236\017J\230<\267?#\2225&\\\322\272?\r\003$\r\3601\261?\233\345\357?E\037\261?@ pTZ\247\303\277\242\246\271\307\006M\212?\020\261@\326a\004\247?\177\332\025q8\007\227?(ZW\277s\246|\277\210\224\034\361P\031E?\220\307I!\362s\251\277\371;q9ry\253?\\\232\226$\366\313\232\277>\033@\276\ns\266?#|\375\335\023 \301\277t\353\377\206*\005\300\277\325\010\377+\300\376\266\277}o?\206o\013\271\2774\373`\201\346>\274\277\272p\243\262\231\241\215?\010\376\373\263\001u\257\277<\025\263\303\313\326\323?]\261\325\337\305W\261\277,\016\224 s6\310?O\353}\244\237\210\265\277\276\357\332l\027\356\316\277>\264\342\343x/\234\277\222\034\312\341it\300?\013`\340$\nM\252\277v\301\266G\3021U?\260B\010\322\030X\303\277\320\262;\377X\367\235?2\000|\256U\306\220?\246q\306\361\241\004\252?|B\272HO\263\241?\243\004\340\206\017\261\263?(( \223Q\241\303\277=2\311\244z,\227\277i\361\210\032h5\331\277\327\336\330\357?\315\264?\010\303\033\025\000\014\266?\342U\206\310:p\265?\377~F8}P\252\277\240yJ\371\tZ\321?s{\310t\333\244b?\221\243\330B\313\316\301?\330}\3674\353\200\215?\220(\342\232\342\213\243\277\305\270\222\nKY\267?H9D\201\357\010\237?i2\225wX\361\242\277\212\346\217f\341-\237?\004\243\226\330\r\227\212\277\227\355l\255\224\036\240\277E\251m\t\364\020\237?-\007\010\205-\343\323\277.I\263\330\343\266\251?\014-\026\025\365\020\273?\245\216>\205!bz?9\324o\230\033\223\267?mW\027U\023P\271\277\251%\241\204\374\026\255?\226K\177\301\005F\277\277\022\301?\303R\307\235?\241 \305\237y\005\277?.tyP\262-\250\277H\267\002\220\200\245\301?\025\272\013\031\365\225\300\277\225\'\375\332\216\343\262\277\'\014j\037\244\263\246\277\3007)\325\\\347\266\277o\245\014\002_S\265\277S`\240\036o\024\246?U\245\321\013\275\331\234\277\363q9\361\307\225\264\277\024\224\231n\375Z\263?1\321\350\304\251\r\263?\324wV\005\007v\274?\253\236\205\300\227\343\254?\306\262\227>\330\030\264?\313\215\275<e\344\243?\373\3533\225T\371\261\277\226A\311\371\351\266\272\277\225\257\323\"\266g\201??\364:^$\272\322\277_\216j\333\344b\234?qPaH\316\226\240?\361MU\224c\314\270\277\320\025\320\001\373\252\231?\220|h\020\204\333\301?S\311\317Zz\261\260?f\355\223\374\333\234\263\277x\325\317\031\032\206n\277\007\032\243EyU\245\2775Mb\2615\337\313\277\003&B\013\313\201\272\277\215\2137\337\207\274\247?\205h\327k\005\361\262\277\223\003z\342\317T\320?\244]\247\026\033\340\266\277\254\325\205o\351E\233\277\271G\335\016B\034\264\277\255\324\017M\237\215\304?\212\n\0048\361\002\265\2773f\305\021em\256\277\346\300\236\311\021\035\241\277\177\325\223\370\232u\247\277\376\324\224\376\244K\203?\246\260\207uj\'\307?\370\2345\314\334\345\245?O\n\025\212\221\245\247\277\257C\235;f\335\231?\311\251_\013\342\376\203?\003;\2524\364\215\256\277\362\366\\R\031C\254?\254\232_\316\373\022\266?\223S\371\034[\352\207?Y\010l\335\335\005\260\277pU\005\3366\213\253\277\3705\024\350\002T\327\277\320\207\216i\371\014\273?,Zc\3422\352\243?BM\345@\0216\253?\037T\320\202\177\350z\277\331\000J\3543\300\265\277\202s5{\'\352-\277\253%\"\305\334\005\240?&\335\021\344\"\022l\277go\337\032i\351\300\277i\037\270\227v\224\231\277\"\301\023W\242}\312?Z&C#,Z\272?U\331#-\247Nz\277\315\314\227q\271\220\213\277.9\002h\306\354\250\277\212\313\322\377L8\273\277T\250m\367Ft\311?y\264\0016\320\354n?\374\331\254\224\356\362\233?\002\224\210?X\250\301?\250! \264\223\010\254\277\357\363\321\301\344p\252?x\254\225\263\304j\303\277\343\262\033\221\3505\273?\251\014\203\226q\343\313?\363l\267\341\231\322\213?\003\031o%\330\317\315?\252]\025C\244\267\274\277\010\204f#\001G\207\277\177\332\275\345\2459\244\277j\226\032>1\216\277\2778T\232\221r7\313\277\251\261\325c:\270\233\277`\221\252J\036e\261\277\221iF\037\306\254\246\277\263\025\234B\274\006\205\277Z\224*\030\325\200\261?\244\210\204\211\374q\225\277 q\334\035\1774d\277\346\341\206l\037\252\220\277\306\321!\321|\311\273?\2730Z\r\034\330\262\277w\222\370\201\345\265\257\277\212\212r\272x\261\226\2770\352\326\017\211\310\263?\246U\265>\255\\\324?\017\203\024\265\376\350\255\277\250S\341\026\217a\300\277\306i\221\016\263\037\223\277X\237\326\030\366\337\242\277\274\033&\364^\223\025?\222\245S\307\305r\264?NR3\334\\O\300?\254p\243\365\023\216\243\277\213\264\247\024\017Z\230\277\270\253\333\304\027\333\236?J\324\373\021j.\306\277\210.\236\362\232b\262?Y\202\376r5\007\275\277S\237\277`\343\352\271?\022\201\347T\213\312\276\277\206*\313\020DXi\277\010\254\215\026\035\247\270?\266e\027]z#\310\277@\236\rX\246E\267\277\221\217\375\255\361\273\235\277}}\022>\n~\304?$\231\007\262\242\347\255\277.\317\316d9\336\261?\022\326\001\022\312l\273?f\247\327@i\323\250\277\227)\360\016\352H\246\277\375\364jF\217\336\310?\377+\204&\314\256\320\277s\\\363 X\367\262?\200\024\217\001\223\315\270?,\333\277\261P\010\251\277\242\376=\031\000D\254\277\217\240b\370\363/\240?\225~:\224d\000\262\277p\273\324\300\352\211\256\277\371\350\332}\263\010\266\277Q\231!\320\250|\274\277f*\307\244\240(\244\277\340\0102a\320\254\305?_\036\255\365\003+\260?\261\331RS\264\343\273?\243\017\331\3350\035\306\277\200p\036\340\207G\213?\222\231s\005\335A\243\277|\347\263\347\241\035\241\277\306\320\004\010.-\253\277?\017_\033\214\n\266?\265\257r)yR\257?D\272\340\030\242\336\213\277\n\227F\200aAw\277B\3279\267\237\350\213\277\n\031\327\302^~\246?\327\242m\302#y\266\277k\026ra\3604\253\277\354\272P\373\233x\220?\327\361\363fZ\007\242\277\n\204t\252k\202\270?\275_\234D-0\276?\314\312\207\324c\311\271\277I\276\034s\253=\303\277>\272\255\364]\253\257?\'\363>\367i\002\200\277p\2528\372m\320\261?\201\240\245\2514\001\226?=\220=\306\203\t\273\277\021\322F\354\314}\240\277\033\333\263\273pI\242?Y^F\273\214?\232\277\261\343E\230\0338\241?\\\212\362\325\342\032\314\277I\305f\236\244\350\263?X\3760\"\027\014\314?\267^>C\"[\311?\317.\377\271\035\361\261?\014\216\322\312[@\224\277}\033Q{\210\234\225?S\323\205\240\217\004\260?2Fq\032\n\274\243\277\3375xz\254g\271?\325O\255\304\226\027\303\277\250\234\"\366\354\037\220\277\357J\312\217b\373\236?\257HI\017\037\363\322?\362\003\216v(R\226?\233EP\007F\036p?\004\204\276%\357\365\267\277\250\302Y`}\375\260\277\321\321\340%\034f\322\277io\334\363\247\337\216\277> \366|\225\225\246\2774\330\356\300\335\363\222?\237Z\242\306\337-\212?\310\335\320\177\305Zl?\323\270\231\002\354\230\314\277\223\372i\321\232\024\302\277\354\321s\3553\361\260?\322\3262&\331\300\300\277Ch\317F,p\270?\231\212?\270\007\207\274?\"\251\311\355\356/\235?\303\247\016\027\255O\303\277\206\246\340<\311\005\260\277m\000\305h&Y\253\277\324\267Lz\261\007\237?\250[\213\255\311\244\303\277w\340\202/\006K\265?\262\303!5\r/y?%\374\234\0314<\254?O\n\231\255\375D\252?L\316\271\211\321[}?\224\003\007\257\203\266\325\277\320\272\370\377\010w\247?\210\355\235k\314/x?639\326#\324\253\277\245w\2171\231L\254\277\361o!\245?\251\262?T\305+\231\017>\302?jL\025\024\365\026\232\277\262\217\231\337\362\035\276\277)\332\224`\346j\226?\234\227\343\312G\270\272?\276\023\327N\232\013\221\277\350\023\221{\210\366\256\277\334\34280\355\333\201?\001\371\261\2326v\271\277r\242G~\355p\301?D\241\370\312\314pr?\030\370\321\220\227\240\244?-\264\010t\244$\230\277\345\250.\305\234\212\222?\373\023\026\321Y%\275?\022\221\374\304N\322\275?\026\320\232\274\363\024\315\277\255\355\3503\3435\302?\001\014\"i?\371\242?\030\332S\252\303\000\272?\364\343SIb\317\212\277(\340\306]\205s\300\277\032E\306\204\253N\314\277T\235#d\321\201F?\271P\206\262?G\247?S\302\210\344\231\t\272?\262FSRH5\310?e\352\251\032\002\260\242\277\347\342\325\0029Y\315?6O\203-\316q\270?\264#*W\2201\267\277\026\014\331\361\274\266\262?\225\246\332\020\251Y\231?\236\024\021\326\205y\307?\344\0244eo\227\272?D\023\010\302\312\000\227?\250`\336F\344\036\264\277\026\213\2342] \337\277\215E\350\310\235\222n\277\276\223\262\n\\\023\232\277\316\232Nls\226\230\277\221\212\360\0130\t\300?Mu^cl\361\253\277\244\007r\225\215\006a\277\362$\305\347\243e\320?\375X\030\016s\000{?\220\022\3035e\277\265?\274_\370a\314=\261?\342$\202\327\020\277\252?\200H\326\306\2436\256\277\334\017\331l\312\006\303\277 =:5\251\275\261?=@\343\365\323\325\270\277\020\t\320\000\341\016\262?x\272\212X\032$\306?\347\344\\\254\300P\316\277\252I`\204\343w\252\277\207\227i\245t\266\304?\0034\027q \354\243\277d\341&\377\363b\255?%H\026{g[\254?\'\360\037\021)\266\304?k\'\320\033\212O\303?\362f`\023\221<\200\277\023\026\341\334<\251\275?HY\314\232dx\301\277\357\227d\021\010\016\232?*h4\177\006a\277?Rt\310\021\226\227\301\277@\261i\315\205q\211\277\274uEV\253rw?I\243\217\035\222\205\276\277:\234\323\215\016\306\275\277\2268\216\253\037P\264?8Rg\333\203\350\273\277\261\002h\022\034VA\277\262\252\325\314J?\276\2779\324\201\033\270J\216?\315\032f&\232\341\243?\276&Vu\233|\306?\301\333\225\000y\016\232\277?M\034\221\032W\257\277@\250.\303\224C\301?\262\235~\343)8\211?\237\265\225\014\347&\235?\36240Z\034\031\260\277\210\002PH\354\336\313\277\250Y\344\2710\032a?\214\340A\340iM\261\277\233\031\275L\026k\306\277\003\215W\277\216\232\226\277ezA\267\005\234\201\277\212\266\366\317\351\362\267\277\216W1B\024\251\321?\233\223\263\343kV\252?\243\021\212\221{^\310?\362\306@[\362=\246?\225\370\353\207\0041\201?\177b](\240\363\242?\333nO\r\"\003\217\277\274]\366J\024j\202\277\021\310\307mR\365\221?n\2725F)\001\266?\330\216\002\207)\325\273\277\341\002\353w\375w\260\277\210\354\337\003u\201\255\277\220\t\305\225\317\314\255?\314%\350\240\310i\300?\275K\231<\021\361\264\277\223\010\3531\021\027\255?}\377\364H\234a\301\277\377\005>\331-{\302?\312<\343\360\225\303\267\277\313\316\233\226m\371\207\277X\243$BrY\257\277(\373\3611Mo\204\277\231\302Ok2k\232?\266\023\210\306\345\347\226?#\277\306f\321\213\221\277\317\374Up\270\231\256?\234\302~$?\253\243?\215\300\352\330\232\313\300?\2638\355\3122\005\253\277\200T\301\021+u\214?E\341`\244\226l\233\277S\352\311\343\314\035\306\277\251\217c\352\301\223\312?\256}\372\344B\231\220?\022\200E\025\230\254a\277I\035\315l\324\222v\277*c\214\376\004\237\247?\314\272fE\024\270\322??jB\265\264\353\263\277V\326.\230\250)\237?\321>\323\226\202S\317?\342\316\035\336\033C\322\277\242\341\030_\360\341\221\277\r\325\250\021\021\233\305?\314k[\235\251V\302?E\206\363\335U\320\320\277}\201\231:\260\353\230?\222\222\235\271E]\243\277\272\314\270x\275J\311?*\365\002\255\243\266\235\277\270\364\305\212\254\220\266\277/h\247\332k\256\267\277\267\374x\340r\330\217\277>\253\252\242\026\366\246\277\225\255\375u\316@\253?\346\"\224\353\037\001\303\277\261\223e\333xU\235?\'\207\223=\316g\234\277;\274\324+\270\022\303\277M*\351&\323\320\267\277+\320\366\263\226\373\260\277\332\031\3259\304\003\300\277\273\363<\303\273\333\303?\355\375\025\230\372\336o?vFU\253\270\021\262?):\242\024\250\221\315\277\315\363;hRo\260?\253\277\024\326\177\273\204?\213O\2779\317m\320\277\3038lw\355z\324?|\372\234\367\335a\235\277&\242\237\220\014\264\257?\302h\310\241\3108\306\277\227\316\006-@\007\250\277\336\301X\350\034-\257\277Vo\240\362F\353\270?\367$1\336\360p\321?A\365\031\200%V\265?\275H\323\2024\207\250?z\277Wj\311=\242\277\001vna\371Dv?\375\3548\352\355\261\233\277e\226\306\343\273e\262\277\006\336\265[5\302\323?l\002Z\301e\014\261\277\004\242v\271\017\321\303\277l5\246\252\273\241\265\277\362F\374\327\3440\215?\034i\207i\202^\241\277\226\374 \020D\226\220?o\r!9\204K\220?\3505\343r\365\027\254\277\n\343\'\323\034\303\265?/-P\352\332\262\261\277\247\271\252s%?\301?-\366G\024\260fo?\364\030@\013\211\276\266\277,\356\267\355\363I\264?qu\021#\267s\202?\366UL\310\261\252\242\277\2429&_N\266\262\277.\302a\340U\252\305?\272!D\257\007,\263?WzE\352\301\312\230?r\370(6-\026q\277\366+<\302\027D\264\277\200\230\027Ag1\307\277\000RB\362`\340\311\277\351\253_g\242\276\262\277\327\304\236\203\304\342\251\277\263\343c{\034L\260?p\306\325\023(\274\252?4J\277\\Gx\227?:n\'j\203\373\276\277=\0211?J\207\305?\310\314\337\211\035\006\301?\031\355~\331\367\324\212?G\356Q\233z\242\217?X\337IQC<\316\277Z\273\306\335K\373\273?\375\322\031\314\024\246\305\277\302Nw \342E\323?i\343\351x0\373\261\277B4\210\304(z\254?\206(\021\371\240\257\242\277\255(\254\2129J\300\277\263\371\243\211)\316\271?\311m\0160\206of?\352\260g\376\352\350\300\277\311N\223\205\021\340\254?\274\272\310\301\220\331\263\277=\371Ls\356T\257?\221j\032\035\321=\305\2775\366\362\313\355\271\304\277KKh\"\027\210\307\277:c\000\253\337\330`\277\034\363\244\372\306\324\272?$h,~\377\223\250?\361\240LDi\321\245\277\000\363{\343\\\013\273?\302B\336[u\257\273\277M\215P\251z\177\236\277\327\210w\241\272]\254\277\0262\00228\006\277\277\373Z\303&-\343\251?\322\331O\250\257W\261?\325\2268\223\367\312\230?\231F\342\215\220\036\241?W\270\362E\251Y\246?c[<\320\370\360\307?\023\t\345\354\237By?\036\325\\\t\372\373\277\277#\010\r]\244\n\266?\366b\241\3476\235\262?GF5R\266\343\244?\340vx\316W\035\226\277\304\254\351o\212\320\316\277\364\014\223\026\014\246\267?\347\216\021\362\031+\241?!\202?\023vy\272?\222[\256\262\276\256\255?\026\033a\241\223}\277\277\247\363\345\303\202\317\267\277#\250P4o\303\256\277\0019KP2\265\250?\206x\355\355\377v\256\2774\327~\017\256\331\220?lj\240W#\235\234?\345U\243\026a\354\257?\007Zt<\337\343}\277\301y\255\314\363F\241\277\336%\3776\'!\271\277\230\017)\217\365+R?\350}D\210\005\377\246\277\256\310\\\272\327\024\216\277jl\242\311\225\177\241?\345^\372W$\263\274\277\213\253\014Y\\x\273\277}ZI\246b`v?\342P\252\354\204\377\311?\343\026\330V\324\t\244?\230\267c\210|I\300?\265\364\252\233\002\246\267\277\037B\303\201\375\343b?\236/\027\205\303\210\323?\357\241a7\260E\331\277\352\234a\326G\240\262?\335\226\320\024\243\"\254?\313\340(\320\305V\260?\376\342\211(\013_\245\277\375\371\307\217\023\310\265\277-\322\311\254\031\350\257\277\215\352)o^\373\215\277\216d\020\320m\260t?-y\221r\337\004\310?\306\303\233\016\357b\265?\205\225U\r\361\242d\277A\010\342?\315o\201\277\243\2459\260x\301\226?Z<xj\251\237\301\277\360\263u;\270\205\266\277.bE\251\377\034\301?f\347\306P\261\035\212?(T\311\275XT\240\277\322q\026\311V\340o?\220\343|V\212b\272\277d\306\347\376v\023\276?\353\302\370\230{\031i\277\302\254\271\220z\342\321?\345\232`\276\356\347\273\277\250\277\377[\375D\263\277\017-\374\236\nY\314?\013\204\375lf\335\261\277=bF\223iR\225\277\347\004\005Z\tZ\265\277r\377\250.\'\360\226?\'\000>?16\244?\340\026l\366\375\266\205\277\235D{\023]G\222?zs\035f\250\337\265\277]\354\251 U\000\236?\261\237\204(\255\301\240?V\206w\016\006\346\276\2771\307\213\275\234oT?\374\023\263\200\266\321\232\277, \374\001,\232\267\2778\217J\340\375\351\270\277\000n\272\365\370\351\264?,\274\324e\025\n\204\277Y\333n\305\240\352\270?\001D\305\r\371\021\305\277\232u\217\021\326\270\272\2770\342l\3656\272\274?G\250\250#\353\201\262?0\373 \275\227k\276?@\361\263\272B)\302\277po\330\211\033\020\260?A\202F\000\223\307\325?\022\223\247\321\217\221\261?\362%\215\200X\370\274\277\370\336p\333;_\272?\276\305\3571\235\275\240?\257c+\316\244)\261\277\277\262\013\t\230\250\250\277\345c\021\271l\200\237\277\361\301\361%}\006\275\277N\363J\200\272{\224\277\306=a&\376\314\256?\241\003\310\237\373:\260?|\330\246\251S\002\260\277\336\017^z N\272\277\244\2053\336=\233\271\277\350\334\000i1\002\261\277=\375\013fe\267\220\277A\211D\2604S\310?\245z\007\360\367\177\213\2772D.\203\360x\243\277(e\022\265\364`\211?\2337$]h\200\236?MBC\306\232\320P\277\216\002\252N\"P\231?\350d\242\356+\332\320?\356\221D\272\355\367k?5esk[\324\253?\376O\232\207L\210\310\277C\004\252\240\371\361\241?\306\034\371)\307y\245\277\'`\210\303H\364\276?\313\023^\352X9\251\277\277crv\337*\244?\361\n\373\235\004c\226\277\223e\004*@\215\304\277\'\254\372\312\237N\243?\312\213\375\021K`\242?K1\211\327\317M\257?\200\226;\371\233x\221\277\212\365\204\002\3443\304?\322&\332\\\350\223\255\277uJA\354k\007\253?\301\307\346\247\\&\310?4\t\233\204\352\234\250\277\211\351_\365=\341\260?J\201K\231\302\344\311\277\360\267\334\017\232\002\276?|\323\316\313\";\262?\342{X\361\347\360\273\277\364\347\361\263\rK\271?\232\004\317\256mO\242\277\330]\261\307\036\233\274\277\014]\244Z\356>\214\277\2342\322u6\334\224\2779\306}\354\217\273\233\277\267\016\331\264\367n\252?\336\005\351\203q\233\253\277w\001\213\210\363\312\221?\371\366|\200(\276\200?4\325\245\036\341!\313?\355\'\206DZ\254\270\277E\304\220\005=\352\300?\025\276\023\350_\330\243?\215n\306>\336\242\273?\"g\212\354\313\343\264\277\356\024\214\376\275\221\227?\323\225\214\331 \306\266?\263\201\323\n\240\364\261?|\200\363\205\202\323\300\277\337U\300\344\316\217t?x}k\035\247w\262\277\254l\335\242z8\236\277\rh /\250\315\274?\331\313\266\355\377;\302?]\023F;r\204\227?PAL`\214Wq\277]vZ\2268\031\260\277\210\304\236\374\325\017\320?\260\307\374\353\317$\256\277\177a\313\2612#\233?\266raN`\352\300\277\330\265!\034\256T\255?B%\366\376\225\270\223?t9O_\251O\242\277\3203J\266\0266\240?Zn>.2\243\255?\272w\2252Wr\217\277\323\314\031\307\007k\277?\255\270\376s%\357\260\277m\307\rs\217\314\203?ry\251\3252/\261\277\202\206\272$,y\311?S6\240M\255b\233\277P\222\332t\014:\247?p\345\214\3171\001\216\277v\200\341\322\343\264\224?ZkZ\021\205\205\274?\317GK\270\341\377Q\277!\306\003\033\212$\331\277e\375\334P\t~\203\277T\344\210\r&\030\301?\002aRS[\234\231?!\330\344?/\241\303\277\377\013r\202\324<~?\207X$\304\253\331\273\277\024\237\340\026cs\210?2\222\030>\334W\241\277:\304$L\365y\275\277\345\261\242\236\t\246\230\277\365V<\327\236h\305\277\'\236\374@\004\244\264\2775\350f\236\322[\243\277\201\270\3127\\\340\312?\340\030\366\236\216\262\267?\211\354Y\247\211\323\246\277 \314\252\312\360\004\326\277\305\227L\346\r\017\301?\264R\266y\325\372\312?\374\322\274\275\000\251\244\277\030^\354\243\261#\265?\\\360\261s\3335\273?\353d\025\270\334\'\240\277\343\254\233fC\030\230\277\n\2327\222\006\325\264?\030\344\207\031\253\326\201?\302\2350\3316\037\302\277%,]U\031G\241?T\256\304\r\215S\201\277EY`\315\342F\301?\260\263\3562\256\275\243?`F\303\276Z\307\251\277\312\215?\353\031\'\234\277\371H\371\226\235\204\241?\334\003\357\004\366,\326\277B\324\373\331\312\260\266\277#bUR\222\333\246?\373\r\245GFFV?J\220\256\236{}\270\277ck8\317\363l\306?{\302\234\201R\027\302\277\021\300\021l\223K\260\277\342\260)\034A\377\222?\367\311^j`\242\240\277\246\204\303\332\205J\301?\340\350_\225\r\215\254?\305E\361\276\227a\222\277u\317\204\245\300st\277\257R\375,\033m\273?\350\031\215$K_\235\277.eh\213Rx\247?=&E\013~\250\242?[[\254\244m:\271\277\245\204cA\224\201\242\277\355\261\003R\370\320\322\277&\214\270A\336\326\300?L\345\215\352\3371\260\277y\226\220hw#l?\003\031\270HC\360\231\277\357\021\215\344\373\266\265\277\014\340-?\325\206k\277k\301K\320\356\300\227?\310\271$\262\262\326\246?)\025f\367\345\203\256\277\335x\311\027\254F\315?\331\210(\273\247n\305\2776\002Y\376w\273\254\277}e\336\302\344\352\261\277\315\025S\346\350\206\251\277\214\376\010\201\236\260\331?\35314\003Jb\227?\275&Y_\232\332\303?\003\237\355\260\222\030\304?\027\005\251x0\027\303\277\212\252 =\325E\261?\326v\366\312\264\363\273\277\336m\006\201/v\245?\372o\222\374\340X\263?@\243\n\030\306\\\232?\306\333\305\212\214\367\303\277\247\206\0332u.\260?\255\302\377\346 \265\214?=\332>\236\200x\276\277\305\\\206\260\013\217B?+*]}\222\352\275\2777\314x\017>\265\302?\2530\3275\037\243\242?\223\330\025\326\327(\266?Iv\273u!\303\255\277\342\330\334\320\024\266\242\277\000\213t&Y\200P\277Y\201\203\253\332\316\243\277\213+\241\213\014\213\302\277E\340\\\330X)\267\277\324Z\007\\\032\263\300\277\237\007E\327\030\007\312?\275\337\206/\233\276\267?c%\000\351#>\275\277\335n\337\247\265e\266\277I-s\315\025\351\317\277\212s\022Z\206\261\307?C[\0342d\030\267?\n\322lH&`\267?\247\213\212\322\320\001\262?\031\006\241\356\312\310\305?\2343\346\364\342\325\207\277\034\263\2175\204\216\205\277\360rp\203V{\313\277@\275H\303\211\223>\277DO\t\264\204\334\252?\304O\271\377>x\275?\321q\005\256!\306\303\277\346Pr%\256\202}?\246\242\310\324\255\334\277\277\025nv\304Hb\271\277o9\205\307fS\227?\354\0243\256C\270\231?\372\222V\r\305E\303?}g\021X\346\017\321?\302r3\273\211\014\253\277\220`L~\027\242\255?\223x\270H\233\251\241\277j\322\243\033~\377y\277\245*\377\326\314c\250\277\220\346/$\256\301\242?\375P\324\360\036n\327\277\023\037\352Q5\300\270\277\242BS\207\274\021\245?\310Z\354\327a\233\265\277\3510\007\207\375Q\271?\017\037@\237\246\356\220?\264O\342\255)J\247\277 =Ph\316\305\247?.\353\367\363\276\233\253?\260\031\240u0l\273?\024\215\001~\340>\261?\030)\370YG\277\241?\263R\211\267\242\223\267\277\245v\347\277T\213\272\277\\\235\021\254\256#\312\277G\'\227@\333\234\356>\326<!\345\210q\241?j\223G\201\025\202\300?\326\352\370nH\221\244\277\251\264x`?\223\276?f7\361~\177\254\201\277\245~\2247\366\323\305\277@\273\232\330d\022\212\277voM\215H\304\232\277\350$:9\235\263\314?\200\346\243\304\272\260\207\277\354\213\306\002\310Jr\277\217\321\224r\235\367\330?I\352i\226\032x\322\277\257MW\263\007\230\237\277\361\362\244nJ}\221\277\312YHj\345\t\306\277\256\257)\321\023\321\273?g\235\024\254\2013\264\277\375\357\307\255\226\260\227?\211\363\3116o\332\274\277\207\"wJ\253\362\300?q\377^]^\030\275?\363q\346{\341 \264\277\327\242A\301\341\210\273?\346\007\306\231Em\273\277\212\305\241\2601\310\250?^1,\253\312x\241?q\037\377\231\376a\224?S\025\254\274\374/\227\277\250\315\237$E\\\307?\242\tR\377\205\234\246?\334ll^\027\002y\277\267_\263\350\274\275\246?\227\300\231\370|\327\264\277\306CH\236\\\347\266?2Fqowm\261\277\030Kh\363^\353\265\277*\353E\235t\323\206?\206w`f\031}\274?\265\251\271\277\r\025\243?a5C&\253a\301?\033\375\242\212\261\201\264\277.\235\237C\217\342\250?x\213\035lO\226\262\277m\337\033R\377\243\250?W3B.N7\211\277\210*/<zd\274\277\210\013\233(\344`\267\277l5\356\032\227\241\262\277\257\244T\233\216\354\211\277\243\231\241.L\210\255\277|\222t\004\3048\253\277~q7y\035r\257?\320\271=\361c\223\300\277\215\210\301\250d\ts?\2735~6\245\224\222?\314e\307Dk\036\304\277\301\020\247a\'\202\207?\272\227\303d\000c}?4ZY\317\016\267\265\277\233\010\206\233\355\277\225\277iSr(H\336\304\277\224\370\213\350w\373\300?\014\365\320\210\340\230v\277 \346\276\004\276\017\277\277\210\032\207[\330j\251?\365a\031\347\227\016\302\277\342\367\232\350^\260\302?\024;{\235Ok\264?_:\315\000\320\365\272?\3336,\374\307\254\300?^\354\312\371]\232\213?>D\035\315\253\033\204?\326\207\247\217!$\240?\330\351O\024\003N\300?\243d\363+f\323\306\277\324A\252\370\227\345v\277@9\247\306\307\326\245\277JG\275\355\326\242\252\277\212w#.\250\362^?\264\2535\261#<\245?\241Rdo\356\322\317?\004\026\210\270\312\264\306?\333\215\275y\334I\243\277q\237\365}\"?\300\277/\221\343\20555\202\277m\332\325\210\025\255\220?\317\033\020\007\014\302\264?cAn\205\260\306\257\277\320\236\001\013\257\370\315\277\001\202\255\010i\203\270?\264\326\245*Rk\301?\315\370^\373r\274\263?%\207H\004\010\302\275?\261\177\217W\345\255\255\277\226\260\267\002\277\257\306?h-\353\0308f\274?2\267\177\026M\232\275?\250\232\353\024\255\271\263\277\232u\351y=\264\267?M\321\004m\000\314\250\277\316^\371ekk\230\277\306\213Q\300\0208\260\277\2364y\346\324\270\211\277\262\270vW\024S\261?\316\0178\303\013X\314?\202\251\323\201\217\037\315\277\037\3617<\005\236\246\277-;%\355\256$]?\\\035\365pn\006\325\277@\007%\274\373\200\317?\220\240e\377sr\247\277L\331\016\274b\371\301\277\223\312Rz`M\265\277\032\311\r\241]\205\243?\376\276\213+\356\326\270\277\240UN{\251\033\306\277\020\330\347AFp\254\277,\311\361\230\032\250\243?\217\242R\223\033\273\276\277/V\220\323\227\334\300?;\033\332\260M\250\247?\323\270\377d@\274\245?\037\230\037y\027\344\323?~d\034p\000\375\250\277W\345\247\022|\215\232?Y\205\035\364\333\'\301\277,$\351-Ngc\277v\010x\340R\022e\277Cmqdl\027\303?\016\236E\013\3344\274\277\207\321\302\272U\203\254\277@b*\316|5\217\277&?BA\210v\221\277\331\001\\\2458\207\300\277\013h\301\216\332L\300?\274\211PAxQA\277\024JJ\346Gl\265?R\237s\305\347X\255?f\200\200\010A4\226?,\225\3446\342b\307?\357\2531\316\r\200\275?\"\261\321![\371\265\277\236\255\345\2461f\255?P\037L\247\254\310\250?\300\263\365\366\316Z\266?\030\017\232\010Q\362\267\277\202 V\374\302\342\240\277T\252DdN\267\240\277\254z\010\231tv\262\277b7/\253j\244\247\277<\'\346\337d\247\314\277Z\353\225.n\360\254\277$\004a\345r;\247?\345X\026\344\213d\271\277\022\035\273\020\307F|?h\256$\016V\243\264?\346 \032(\364\356\273\277l\326\001\267}\027\302\277\231\346\"\333\345\337\207\277/\223J\2067\200\245\277\301 \3455\312F\316?\374O\306\274\303\027\260?\315\206\267\213\303\313\270\277\210\344NQ\007\206\235?Jj\003\006\035\344\301?e2\375@p1\301?\271\224\2744\217n\234\2776\005b\245Z\272\233\277\035#a1Xo\231?\304\243J\334U\346\255\277\255\205\016\320\021\307\277?\314\020\370\377\206\022\213\277\247g`\026D \275?\031a\343R\336Fn\277\255dI\371d\363\304?ujW\\\2408\304\277\217\252\367\232\264\266\254\277\342\232\331\214\014\351\242?\214\307\024\010\235\254\262?x\tq\005\005\226\216?\213D\321\325(\013\263\277<\265T\304L\246\227?6\250\306!\365\246\265\277\336\245\307\312\350\362\230\277g\271\262\225O\215\300\277\301\237\361\235\026\316\223?l`f\224VU\274\277 lm<\302L\201?\264\200\002\024h\314\272?\007\301\340\021\030\000\274?\313\010\263%\342\374\235?\225\025Q\034A6\241\277.:\002\325r~\225?\342@\214\235\257\270\317\277\306\020\336wE\260]\27737B\321\013\014\263\277{\252\336\263DP\223\277\211\306&\240F;\253?\326\340\341\337\010\227\265\277`\007\326\277\371\004\306?:\3744\203y\004\261?8\375\326/\r\352\232\277!4\\\030\211\036\214?6\365q\014\210\371\265\277\361\2076\024\010\230\231?Z\342e\301$6\221\277\024\344\020\013\247\232\225?\353\260\255\306\223\274\275?\253\020\017/B\301\274?d\033t\017xr\263\277\361o\240e\206\031\322\277\243|\006\344\330\373\260?\261\350N.\352\231\222\277f\367,I\330\213\210?\230NK\246\363\252\226?=\2343\037z\370\221?\304\350\010\201Zf\246?\201\326\334\273(\010\265?+\324\240\024f\363\260\277[N\374\232\353\027\260\277\246\236\361^G\227\317\277\321\354\035\234\343\373\222\277\003\253\350tF\363\263\277V\305,,\\\005t\277\010\301\330\376=\350\212??\277\340\366\302n\300?\246\023LI\373\035\260?\233\376\177o\0206\262\277\330\276\312\271\212-\271?\263:\037yM<\261?\300u\366\017\320\316\252\277W\320\320%\2646\300?\330\242\364&\202\372\244?\343\261\217\344\033\257\275\277\221\245}\264\375\324\231?\212\341e\270\253\330\262\277\353\033\265\206\322C\242?;^\np\360\363\276?\332-\377\235\273?\313\277\033\257\222\224\254\037\244\277`n\"\256\305+\252?\332\"u\232p\234\305\277\320\3255\\Y\370\316?|:\260f\025\221\263?b\021\214(\005\242\277\277[\026m\232A\371J?\203}\240\236{\240\305\277\337^1\325A0\307?zU\222\362\210\345\207?\216\313\207\276\3553\324\277\035E\256\376\267\316\222?1w\352q-\r\267\277\371\325\303d\354[\252?\315\236;\217\020\343\272?/\007\241N~\037\303\277A?\306&Rr\276?\211\244\377`\272}\273?d\277/\365\321|\304?\264\206\203\235{B\272?\305z\277\210;\231\266?\177\231`\005\n8\222\277\373\217\231C;r\303\277\014gz\352\250D\245?\367\367c\326\344~\250\277*\312FJ\331\370\251\277\016G\342\340\202C\261\277\274\324rys\213\312?\300.\277\003\036\233\312?EZ\002o\021`\251\277\224\360\271\241\030\331\235\277\224\212P\340\010\325\237?O\356\345>Bw\272\277\231\362bT\r\315\240\277\245\242\374<\204t\277\277\201\222\035\347\365\306\224\277``\366\030\234\367\226?qc\266zO\214\270?\337\026\322\314\337\275\323?\275\3751\"\324\237\230\277\020\376\302D\021\366\222?\242,\321I\013\366\251?\316\322X\242`,\301?\371\006[\030\022\276\217\277\002\"\201\0175\036\223?%\362\210\215`\256\270\277\312\256\356\024\202\315\267?n6\321\341\314\262\220\277\023\252\003\231\'\336\254\277\016\245\304O\363k6\277\245|\005\251*{v\277\217\245\207\250\037\375\262\277\376\033\275^\310\305\316\277\01401b\340\225\303?\331)\351\026\266(\274?\301\344p@F~\311?\373\010#\0043\236\263?e\354K5\357\204\254?8s\\\251,C\210?O\345;\320\030S\211?\013T\334\022\235*v?\302\036\375\243\370\271\277\277A\201\000U\372R\223?\361\"\263\350\357\234\255\277w\344U\037\243\'\246?0\351\350/\365<\300\277\356\375\221N=o\274\277/\265\267\021\3731\222?\377\"L~5W\252\277n\230rs9\242\324\277\315<\241\026\204A\246?\304\r\322\216\221W\252\277\236^b\2702\306\233?\031\266\233\212r(\255?r\356\003\007\254wk\277\351\270\327\031/\266\221\277\233\014\014\037\307B\277\277\344\314S\260\364\351H\277[U\317s\317`\263\277\344\010\327>\000#\313\277\023\205 oD\354\313?\203}=w\033\025\260?\2728\000\250k>\271\277\017H\227\233\262\365\240?\365\3155y\204\000\267\2775\345\310e\275\215\232\277\260\276J\343:\200\273?\323\255?\220`E\255?\201+\257>\007\343\305?\354\274{\257\210\023\245?\333\315;\255R[\277?\177U\271N\276\374q?\265\205\270\201\311:\262?\276\013\220\305\253\013\221\277j\016\222l.N\300\277f\227Kz\341g\267?\272\375\302nk\243\260?0\031iv\215\320\232\277n\003\003B\337b\261?\033*y\254w\026\267?\214\256\206\362\r\036\260\2771\215\365\333\216\'\254?\223r\2149:|\235?{\345 \344l\037\262?Ia@\303\335\345\302\277\255\227\333\344\216 \273?c\3709\\\266\206\300?\003\312\237\033\r\255\220?gpF]l\230\272\277\234\327\320\\#v\247?\232\001\266\224\260\330\270\277\310\204\352\232\345\316\266?\332\336\221P\337\tw?\363\227\300\\K\037\261?Q\327N0D\317\305?\311\3456\226\016\213\262?\276A\331\337!V\274\277\255\274d6\327\324\247\277\257\321\375I\003S\266\277\023?Y\240\350\317\275?\270)r\027sR\262\277\334\273\216R\355\025\200?\017\210\232\356\372i\320?\334\230\231\322NI\267\277\032\214\357\227\007\377\251\277\252\252\270\375\016\310\306?F\201\316\316-\215\252\277\323\272?\360>&\246\277\316_\'\271\212\201\257\277\307T(\370\260\006^\277\017\231)A\013\217\236?\304\274Iu\301\272\253?\203m%:\230\236\235\277C\341xIy\222\260\277x\327N\221\327\334\224?G\276\225;\004[\245?\213<\217\036\252\241\303?\255\254\010?\356\201\241\277O\250\277\247#\322z\277\270\205$\022\270\231\212?\006\327\335\347\n\327\274\277te\321\202\306\356\225?\236\342\274\372\215\206\263?fJ\017\300\235m\314?\205r\010\267+\262\266?\307\221\317\234o\304\263?\2133\255\241\2574\211?r\203\360e\223o\231?\317\374\336|3g\224\277\314<q\200l/\270\2774\223z\260\262\364\306\277\352\361:\203\223W\247?m\t\257.;\227\267\2778\025\344\277\3568\262?\034t\330\024<{\262?\262\300i\213\357\310\245?\334\266yvk8\257?\243T\032\357\343k\304\277\3018\317\255\256\261\210\277\235#\224\235j!?\277\345\255\350l\000s\264?}<<\260\302\327\301?\375\',\334\215\314\224?\340\226/\320\234\023\265\277\257\255?:T\247\307\277a^\301\340\317P\244\277\341Qx\276\026\353\260?7\301\213E\2261\265?8\013-!\210E\204?.\026\231\306\037\326\\\277L\010\244\226g\027\275?Q\245\177\236C\306\245?\374v\267\331Z\301\225\277\374\323\034<\004#\255\277\301\241\226\366\305\315\314?3\364\251\222\2653\263\277\256\253\323\236\006\027\272\277\035\327\022/\233\224m\2779\207A\347\274-\301\277sK\215\344\253\'\261\2774)\035L\326\303\302\277K\315\224~w\251\265?,\347N\'@\363\320?\365\327\177[,\223\236?X\317\024\332\236\000\242?\377bP\267\222\377g\277r3\261\377\331\226\301\277\366\237?%\245l\257\277\345\375D\020O\332\262?\014^\237|\354\346\267?\017%\363\315\027\227\275\277<~4U\026\217\265\2770\003d\031\225+\317\277lL\013\353{\262\215?\322-[#a<\304?\025\363\347NOJ\261?\3015\004\211\207\354\302?;\357\275\'\177\257\307?\t]%\364\360\370\265?eN\223\231\214\n\260\277;\232\246&\247\342\255?\315\333\232\335\356z\311?)$K\212m\323\305\2770\030\251\265dL\300\277\013\321\353\034x\343\263\277V\227\232gX\333\273\277\272\260D&A\255\324?\334LKX\332\366\242?\225\356O\237\024\316\276?XJ\027\'\216U\220?\375\240(\246r\000_?\016k\000\320\310\327\262?`\230Hf\027\'\236?T1\211K\225\377\266\277\225^\t\227vA\274?\251o\360\214G\227\256\277\t\010\354\t\221\334\233?\002\323\026\014\361g\250??\027lA\324*\236?\371U\356F\313\322\304?\256\245\355\243O\352\321\277\271\252\272\305\032\000\264\277\2627\336\226\243_\255?H%#\000\212qi\277\240\246\231\016y\016p\277\004\262\276v\240_\230?\265\031\306\241\007\272`\277o\210\367=\240\366\236?\255\000\210\326\031\026\310\277j$\332J+\377\226?\336\3406\217\364\230\303\277\025\363v\034\273\275\234?\312\007\343T\313~\205?\322$\301\t\314\002\304\277\224\216\014\030%\345\214?\237\0239c\317\262\264\277\327\314qt\236x\306?%\264D\244!\325\301\277\0315\006\024Z\242\302?,\272\246d\2430\272?\307\310\361\017\3037\265\277\255;\214\251\243\220\256?\221\210\304D\222\347\177?\340H\332b\237d\266?\247\217c\177i\254\250\277\327\334k|\371w\241\277\341\257\032gud\231?m\263\364\303\206\021\245\277\030\273\263\013\253\265\304\277\207\270\313:gL\265?L\353\231\374\265t\274?\373\335\2007\037!\317?7\371:B\202\345\267\277\370\037\256\032\373\211w?\313\370\377A\367\366\257?\347\326r@7\216\276\277\321\355\013=83v?\246\266\t\305^x\265?V,A~\302\206\265\277\000\257\221]\273\322\263\2770\206h\201$\350\232\277\023\rX\301\357?\256\277REz\216\315V\261?\324_\300\263\361\221\262\277)\231\276!\225\010\306?rA\2550\001\357\264?\340\260\002E-5\262?~\216\242\335\327\000\267\277y\240\006\202\246\334\275\277\037+p\301\323\372\265?\017\221R\361\306\274\276?\275\336 TR*\237\277\334\313tt\344\272\321?,\033!1\247,\262\277\361_\362(2\357\214?\025$\007Z\035\202\244?\310d\'\206;_\307?\324\332\035\222g\206\204\277\270L\3724\310R\260?[\204\213eb\350\300\277>\326fX\350\331\307\277\223[(\374\376\272\300?\177q7h\\\002\222\277\263r\002\244I\253\241?O7<$a)\262\277\263\371\"\334$A\266?\373\023\367\243_\325\303\277)\"X_^,\304\277\354\240\334Sg)\203\277C\356x\360\201\253\314\277\263\370\343\331\025\001\242?\300\326\000K\'\320\220\277\2464P\023*j\303\277\241\323d\331>\006\301?k\357\365\001Q\241\266?\020$\377\366\r\355\273?!\004H\255\236\007\267\277\230\022\343\300\347\267\317\277w\234\300\342/\014\272?\207}}r\332\010\264\277X\003\325s\337\006\307\277\213\235\277\277\251\332\234?\205G\037\247\324\310\223?=\314>\351hh\325?z%<F\202\315\326\277\316I\r\344o\366\261?\"\200m\200\212y\231\277\257\'\224\240\021\247\272\277\364:\346\335\347\320\307?\342\324f\362{3\261?\031\243\350\"\'\nt?j\324\237Q\202Y\272?\213\354\341\224\035 \221\2774PQ\310\254C\231\277\251N-\315\017D\304?=\261\303\n?\036\237\277\335\244KB\024_\252\277e\341<k\317\215\316\277\207T\276\252\034\036\262\277\034\242\343\256\223\373\222\277\007w\375\031NK\301?\\\370&>\006\233\315\277Ww\324\314\0078\232?\013\177\277A!\246\247?T\343P\033\255\363\214\277\261x\004\t5!\275\277\375\271\020\210\205X\265\277\274Y~\374\352f\243?=|yAK[\250?F\305\207\254\223\367\271\277\272\'\237LA\332\241\277\032G\321\253\005\346D\277{mo\t\237\310\214\277-\261gV\277\331\271?lY\327\3236}\252?\334T\342!\267\032\310?\330\322\330\305\332\306\242?1\010v2\330\363\263?\345\243\254\270\302x\265\277K\006\343Y\375\n\217\277\014\022]\035\353\276\244\2775M\332\273\253\260\245?5\340:|\230\366\303?&\223\263\353Q\206\255\277\227\214|\205{\001\273\277+\03500\307\004\300?n\000^\351%m\311?\025 \335\217\210E\273?\237\203\221*\230\370\261\277{a\224\263\212-\273?\027\232\304\034G\016\311?7\343\377\370m^\227\277}\\\325\177K\212\226\277\366F0\255\234D\307?\307\024.\243\327{\261\277F]Q\224\315\232\303\277\203\305\034N\255\274\261?i\342\210T\007\250\251?AJq\213\313t\266\277x \031b\354C\266\277[S\265\3357\216\263\277\303\nE-5\031\246\277\247\371d<\\\'\261\277\341\234S\003{\203\265\277zG\017\231\331\252\277?\252\367~\0313\373\214\277\363\271\262|C,~?\225{\023\254<\310\270?\257\324\354Wk\210\253\27776\234\374Gg\300?\332\302\311\034H\246\202?\'6J\270+q\214?\356\0316,\314\214\264?R\305Z\304\0103\264?6\304\242\365:#\204?PG\033\003\023\312\267?x\236\336\024>F\245?\030\205\236G\014\366\300?\245\305\361|\224\354\242\277\370\350\277]\022\006\270?\033\003NC\346v\244\277B\214-/\264\035\300\277`I\342v\301\262\242?x\035\233\3279y\211?eb\227}\301\366\235\277\212\250<\303\330:{\277\007S\261O\275\222\255?\300\036\016\257kT\304\277\'\325\334<\013\016\270?\231D\262\004\005\326\234\277\277k0^\372P\253?\365\304\207U^\342\304?~\374aR\217\343\225?\177\202\007\024\020h\303?\313\222\270\201\n\262\213\277\367\347+\357\334O\262?S\265\375\242\327\031\256\277\334\276\0132\362\034\201?\314\263\227\363!\355\315\277\206\321\317\241\002\253\222\277\020\374\234+\370\215\240\277\357J7;\254\010\321?\022\264\263]w\366\251?se\200\310\356\201\234\277\215{\245UG\203\274?\003N\367\025\342B\234\277\322\313\314o\014\334\273?\253\025\350\276\032\277\201\277?iP\302r?\276\277\341\027so\004\232\302\277k\366\250g\001\000\256\277T8FU\265\337\261?\362d\337\324\303\241\314\277\243pTZ%5~\277\264O\002pp\034\237?\0320\362k\257\371K\277\366\005$9\033\013\312?R+C`\362\021\255?>\337\027,\350)\220?jT\001\267?Y\270?\255\202A4F\232\302\277\307\033\236\267\370\316\307?\354\20618\2401\247?\300\231\210\321\346\006\265\277\303\'\001\354\270A\274\277\325:#\035H{\257?up\262\362\312\224\274?\305\354D}\353\023\257\277\207\377|\r\272[\273\277\314^\035\257\360\225\264?+W\271%-#\265?b{\321\272\231\251\255\2773j\307\242Sw\244?$\340\303\312pB\276?O\333\001\273\3516\231?\361\t\223\t\221\006\206\277\264\254\020\305U\344\273\277\231M!\002\300\261{?\010y5e?\365\263?\262\262\332>\315\230\224?\330\354\237\177<\215\232\277\330\350\n\300n\223\277\277Dz\373\021\271D\201?\261\373\373\270\002\223\244\277\251\333L\264}\243\277?\211\245\370Z\033Y\261?\363\302iG\235\372\213\277\272\014\332E\251\033\311?\242\347\201\336Rg\223?\224sO\205\017\237\260\277\222<!Xr\300\314\277\205Y\003\004\274\013\334\277:\035q!\353\260\262?\314\227\r\316\230\353t\277\363,\\\372Gh\242?+\"um\315\254\265?dn\004\252\331\023\275?\235\355\264_\262\355\302\277\203>l\035\2547\301?`\223\010\003r\210\177?s\361\321h$\217\275?\372\'\350\245\3530\302\277qp\264\232\365\353\215\277\220\201\203N\234\\\301\277:\355i\235\3536\306\277\022kVx\305P\300?h\022\031\262b\n\310?\374\266 \376\215\031\271?\301\212\037\345\013\022\300?\271\230\000N\230\366\246?\027h\217\036v\325\246\277;\273B\352~\030\247?\023\216\276\\\363\023\327\277\032t\375\325\372\214\250?\026\003\006\323\341:\322?^\'\244\2063\346\322?\322f\246Q\263\244\256\277\361>\000\022\266+\300?]\263\031\177\027\265\250?E\375!\243Me\277\277\257a\272\211N\322\247?\261\276W\375{V\317?V\253g\233u\353\240?d\270y\013\004\337\305\277\371t\333\366\3151\300\277\031\1770r2\211\230\277BZ\314\377\277\315\262\277\021\353\352\244P\341\227?x\326\330\271\250\263\252?\370\230\037\0258*\267?7w\036\032P\267\304\277\271\257\372#)\364\256?\212\250s\024z2\211\277\352\013\202E]9\276?\334\300\361\372=\014\225\277o\247p\246\345\027{?\n\267\031\036O[\217?\\\301\032\276\355\331g\277B$(\234B\377\270?\310\273\234\210\177\377\246\277\001_A\264\261x\325\2772|N\005\330\267\257\277G\244G\243i\222\256\2772v\305v8\033\233\277j\346\377%\305.\277?\203O\242\021u\177\233\277\227\206fLt`\252?\230\207m\301\025\217\312\277\001Y\023>S\375\231?K\341\327\177\350\220\261?\240\303\327S|*\265?\363 j93f\253\2775b\214\316\201p\231?\331\237G-Ue\255?\352\016`\005O\032\321?\310<\262z\336\350\256\277\247/\007\345$\023\265\277\"H\350A\242\230\273\277\200\226\005o\205\204\217\277p_\244\014\365\025\307\277\206\202xPmB\212?$\205:y\r\377\313\277\302v\336?\035N\305?Iq\\/n\016\275\277\250\307\257\232M\350\312?\323\230\251\212\214Z\244?\371br\016\226\315\261?\270M\364h9\210\310?\374a\266;M\026\310\277Z:F<\256\025\257\277x\353z\366\013O\273\277\303,\236)\222\030\300?A\017\005\211\r8\214?\177\307\026\234\032\010\255?u\242\364:\333z\240?\246\251\216\223\025\343\246\277<\3511\306{\234\244?\355\341\221\273\022<\265\277\264>\332\262X\261\247?\336z\235\325IH\277?\330\023\216\254\214h\251?T\317\371jQ\311\242?\000x\033\365D\021\261\277K\321\236{Ic\244\277\214\024G_e6\240\277\244ZZ\363<\237\315\277+\310\215\235\214\021\250\277\2455\327\177Mf\276\277\266z\'\341F\304\252?\\\207+L-\323\300\277\346\007T}m\001\242\277\201\003Bq\374\220\224\277\201u}N\272\025\312?\006\374N(\316\331\255\277!vr\3125\337\247?\275\365\004G\304\032(?&\373\317dOt\307?\205\"\370\026\337\001\260\277\324\021H\220\307Q\210\277\204\027\256\202\311\326\230?\374\013ge\030\212\260\277to\207\211s\030\227\277:\312\352\276\345\266\201\277^\313\001\273lf\244\277\243\324R\340\361\204\261\277\'\246\345\316s\006\266\277\317,B\027l\335\254?6\260\373u\357\006\251?\3432\222\304\242\206\266?$\006\241\327\327\322\263\277\204=\222\227\"U\252\277\234\316\302\010\343\247p\277\262\327\206d\373\336\267\277>L\363\231-\356\215\277\256\002:\235u\323\274?Lt\341\031R\257\270\277\031\207p4\035\252\267?`\366\247\242\300x\310?13I\317<\317\254?\020\242t\207\246\302\244\277\207\253\216\304\363\033\274\277\213_\313{5>\254\277\257{\207\021^A\264\277KG\031\355!\377\303\277\223Fyu\027q\304\277#f\021\234\0206\205?\210\303\300}\245\001\304?\244\376\001\341\2241\314?\013\334\004k\370-\273\277^\0037:\354\237\260\277Z7ay!\035\301\277\232: \366(\345\205?\3551\006+~\352\272?\263\226[H\\\000~\277ITr\034)\'\272\277\003\364\367\357\027F\253?;s\313Q\320\346\234\277\201]F\245\240\026\305\277\375\350\000\214m\352\322?\311\030h\\\267?\254?y\006\023\364Mu\260?\237\232%\260\355>\226\277\004\332\355HUO\274\277\374\024\275\322\342-\254?\361\022\000\265\340\373\223?\253H\306\206t\002\266?V\001\235\005G\221\270?E0\344\213=A\224?L8;\321\301\027\270\277\213\253\031r\212\273w\277\005\035\256\237\207\035\222?8\177RP\020o\240?\213\274\030\2522\221\304\277\206\314\252\261\206\344\204?\320\033\275l\256\230\235\277\333\303\325\204Y\352\267?\330\203\270+(\"\301?\016/9\316\336\201\227\277\302\242\376\237\225N\224\277\371G\311\204\201n\273\277q,\316\375\261\204\260?\032\243\177\257#\221\270?\331[\013\223B\030\275?\003\255\246\235U.\257\277\217FX\302\261R\211\277\343\001+vm\351\254\277i\252\024n\343#\264?5\307h\216\016B\262\277\017&\217\210\322=\303?H\207#5D,J\277\340i\243V\324r\250?R-H\312\257\307\204\277\244\220\210\243L)\303\277\261\227\365\377#\364\266\277 L\367\024H\372\244\277\361\237\343\217Vx\225\277\r\n%\267n\320\276\277=\251\225af\320\206\277\221\376\023\327(\327\261\277|\345\343\n\277-\316?\260\3170\352x\357\305?\003\353\346\202\354\020\255?\250\275\246\325\234\210\240?\277\26417\033\216\240?\220|\200=\372P\246\277\"\335\002\022\t\305\257\277q\352\017m\0077\263?\016\275fn!\346\255?P\355g\277\372\373\241\277\350\n!W\273V\267?\033s\274\204\227\317\242\2779\301\376A\255\352\236\277\367\240D\346so\247\277\365V\006\307H\304\316\277`\365\311(\0065\323?\242\242*\365\247\200\256\277c\201\002D\021\232\300\277f\031Qa\275Q\302?\030\027\2247p\237\300?\264\317\227\373\";\262?\242\241|\376f\357\245?h2\322D\315\200\225\277w\245$$\232P\252\277\326\223\">O\r\241?;i-kQ~\252?\006\233\221\361\316\016\261?\200k\\\333\223|}?\353\314\027\002\013\311\262?\224\002\037\231\242\037\232?\241r\213\251\274{\312?\235\033\303\255\302\214\251\277\350I(,Fk\255\277$\"\340]\207\341\246\277\341<:}\355\177\214?l\353\237\255\332\237e\277\352\275oX\373\303\310\277MN\030\315\367y\222?3\030\032\236jQ\247?_\226:\354\300\311\253?\307}\266\370(\033\276?\250O\211X~\235\275\2772&\006T\203d\256?\262\374M\267\235o\250\2779\027q\262\037\202\221?L\3321\016\251\203\313?_RMU\376o\276?\260\344|\236\372\202\310?\230\306z\344\231Z\265\277\362`@\374H\260\253\277\2615-F[^\310?\276\3271\026\3131\277?\3441\032\007\363L\254?YBw\243dW\312\277\360\005\243_\260N\204\277b\373\n)\302\024\303\277\177\326\372\3514\276\246\277\334\'\231[o\260\223?&\204d\313\303Z\255?G\312\000\0172\312j?\224\350y!1\305\307?\260\301l\177\227D\252?\2466\024<\333\001\244\277\301\037\313y]G\242\277;\340F\241^\205\261\277\327\244\264\203\177T\240?\216\305\345\310\362\t\300?{\334\312g\320\320\307?\371\373\263\320\226\242\277?\022\027\244Ep7\210\277\350\345\3025\211\"\317\277\003\033\324\006\363>\227\277\201\330\332\310r\000\262?\027\310\324\376>~\206?\032\005\310\331^\356\270?[t\240{&\376\271?f\347\ru\246\227\305\277e\375>0\212>\302\277\034\374\315ev\327\265\277\013C\260\001:\233`\277\006\2127g\250\365\312?\301\333\213\370\215E\301?\335\275\210\302\231L\267\277E\373\';\312U\252?@\006*|\177\221d?\235NT\261r\264\307\277\200$k\336\201\222\265?\357|,\"w]\263\277\001\350\312\256\274\260\335\2779\030\223\325\024\366\236?:\215\362|Q\005\236\277XHIIw(\277\277\325t\232\260\333=\275?\317\001u \006\311\222?\336\346\321\323\024?H\277\257\r\014\312\226\272\272?\355\267R\017\236\031\223\277\223\211D\305\357I\275?WEY\353\355\326\255\277\013\"\023M/\332\274\277\324C\003\310\000\317v\277\313\310!~\2768\300?V\325\004\307\355\366\257\277\r\203\244\325\306\346\262\277D,3\227\370D\302\277<\331\036\211\217U\243?\262\201\246\023\344z\311\277\230\035E(,\245\247?\355\002S\035\311i\274\277\311\315\326\214\326\326\262\277\240B\"\266Q\010\263?hj\370\241\214t\260\277\024\317\204n\261\375\203?\277\321h\321\241S\322?\315\213/\357R\327\253\277\377)E0\025\371\320\277\326\367\225k\310\242\246\277\245\353lfX\'\313?\332\314\364q\366\221\216?\323N\323C\2449\312?wxkP\270\261\266\277\027\314\177\267\242\375\232?\375\366\207z*x\322?]\206\3366\205Ox\277\247J\227\302d\205\241\277\007=l-\356\246\314\277\347\232V\'7\372\250?\344\317\316\370`\256\316?\267\"S\036\016da?\300\005\3257\371\263\316?\251\264V}\032\233\267\277c\307\021U\207\345\320?\363\013\352`2\234\305?\365h\224\n\241\272\245\277\244\003\272.\216\254\273?\271\377TB!\032\206?`3\227\344\241=\263\277\007\2210\350xI\307\277^.\035\010\2103\271\277\303\014\357\242\357\254\254?\277\210C6Fp\323\277\342\261\\\301&i\261?\005\000\321\0219\360\261\277CR\205}\000\005\253?\321\313\334F;\274\327?G]\374J\252x\313\277~\343?\273[<\272\277\276]\261Xs1\240\277\352p\212\276\343\263\230?\360\211\356\202y\345\274?\210O\026\217\344\001\241?\355\377\272\020\021:\267\277\222\032h%l\375\302\277<\375\243\216#\270\267?\021\"R\363\233\307\240?:8\242\245\026[\260\277\217\031\302\232\277P\254\277#\336\377\213\241\252\256\277\315\252\2338\274\262\314\277\313\177\256!~\033\320\277\257\213\022\232\226\007\272?\021%mX\222\203\205?\245X\006\0016\272\264?*\354M\311\007\306\314?\271\301X\360\271\341\260\277[\231g\375S\253\270?\373\016\303\005\210D\235\277\261v\266\200F\216\255?\0311\026\267_\234\255\277\007\256\206,\371\221\305\277 k\241\342wH\261\277\025\312\003\346WT\271?\360-\370\262dqZ\277h\017\253|\220Y\266?U\277\234g\361s\257?\377g\323T\263\235\177?5\250\350\321\256\014\241?y\3249B\312\306\235?\'\332\207\217\205\225\272?he=\214\225\033\304\277\251\003Z\024PN\220\277[\031&\201}\345\260\277\341\217\252\337\2765\251?\200\322\203j\373\264\243\277\370\257\270\003#1\241??\206\315\336Wm\233?\205\026\263 pJ\264?C\312Z\230\221_\225\277C\272\017C\243\374\305?j\006^\377~\257\253?\336\207F\344\241\333\230?k%\300\260\257\305\235?\210aeRr\253\275?1S\347\212\275w\260\277<H\261\177k%\243?\227\201\320%\232 \247?\225\222l3]\231\263\277\352Cc\351\021\330\255\277\t%\373\033.\202\267\277\345\242EF\314\274\225?o`\361\322C\227\265?\026\263X\350\030:\267\277\030\272\205\235\333\223\222?\362\216\263\311\245)\261?1\201\361\330\320\030\316?\252\246\035M=\177\217?3x\2676\266g\270?>\375A\025;\306\275\277S$\371\026my\265\277@\314f\243\2325\261\277k/\250\216-P\261?cp|\333\273\245\321\277\212\254\030\321jo\240?\256l\263\220(\353\322?\321\227\316\220\302\367Y\277\017\253\272Ek\371\261?\177y\001]<\202\241\277\301\230\365\337&w\240\277\020\351\310n\360-\301\277\2043Y\2753I\243?f~\276\371?G\272\277\024\345\255\235z]\210?\256s\007\270\336\253\241?o\205\211\372\242\231\260\277=\310\265*\036U\270?\342J>.~\353\304?\215\r\225\310N\360\256?\215\376\267P3,\270\277\207z#\362\373d\266?\205\n_\014\246i\200\277\242\367\322N\007\275u\277\326\314~H\225r\213\277_[y \036V\307\277\237\326\022\203\036\235\233?\270\263I\265\235\001\257\277\215\344`N\366\271\262?*5\235\276@\350y?\352\341D\010\203 \230?\252\003v\314\265A\324?l\021\350\376\214\331\261\277J\0175\325\316\326\301\277q07\325[\000\310?\361\357\253_\001\335\243\277\247\366\250x\246\202\311\277m~U\340\351\277\266?g\220\265$\313\306\301?\002\236\277\253v\222\300?IE\231m\204\266\237\277*Eb\023x\223\260\277\363!7\202&\314\274\277\375\251\335\017\014\212\205\277\004\007,Q8\364V\277*\355! R\366q?\303\267\272\210[\"\235\277\374\225\302\207\270U\221\277\237\245M?\376l\247\277\272\213V\245\337\306m?Ng\000F\366\316\271?\223\224u\230V\201\247?b@\202\n\256\345\224?\356\233\202\207\364D\220\277\223\253\026\242\n\341\260?E\030C\003d7\256?}\331\264\330\006\233\241?\204\307n\005\3722{?_-\304\266\350\007\220?m\21105\2544\250\277\317\247\257{j\330\214\277t\177\325k\227V\222\277\203M\332TgZ\246\277W\210wAs\000\264\277\353T\217\364\253\361\207?\205\2762\363\246\'\203\277p\317\303\346f\030\241\277x#\020?~<\253\277L\304\320K6\274\211?Z\333\231\265\002m\300?\023>o\320\331/\261\277\376\246\021D\342\312t\277\347\277\345\232\2439\\\2770\352\025\026\313n\302?7\256\254=\014.\310\277R\3013\323\031\267\245\277U\205C.;n\260?\335\212\271\273\002\241\302?*=\346\034\301\334\305\2771w\310\316\351D\240?\327\223\365\020\007)|\277L\262\026\214\301\264\247?\201\214\327\331\036o|\277\023\253t\321\227\372\264\277\262\361\030<\247\255\246\277:\323]\332\001\310\216?2\246\\\270\303\007|\277r\340\361\340H\270\267?\346\247\3725\310\371\216\277\217\327\034\210\215\247\247?\274+&i\347B\255?F\220\010\306\247h\271?\214u\227\233\276\266\227\277\213\001J\332u\302\305\277\230\353\362\341\275\344\266?\311\352z\221=K\265\277_\345\335k\322\003\272\277\305\203\273\227\322\373\210?K\207i|-Y\231\277\357\356L\233wL\226\277\244*\n\233@\254\221?2\204<\354\000\375M?Z\326\'\336\205\207P\277Ke@\373!O\260?C\241q\322\024\"\266\277\350wyC\313\363\234?\"\257]}\342\233\253\277\302\037\362\020?z\302\277\352\260\370dSYt?\306\337+!\307\211\303\277\013bj*\020\240\250\277\340\271\306\334\231\272\261?\252n\234\002\204\215\302\2777\332>/\177\276\214\277fLdZ=\367\255\277L\334\000%\340\312\241?\243\300\025\311\336\342\302\277\tM\215\233\n\267\260?\031\246\335\3278\034\216\277\344\235\206\313\240u\320?\334lyX\245\026\300\277W_\337\241Wv\260?\t\241\005\236\234F\264?#^M\"\316\035\322\277\3224@\241W\377\257?L1\205H\313\242\267?\237\371\271\354\333}\260?\177\245\334T\2643\236\277\236+o\017\022\342\271?]\022\247!\273\263\305\277U\2558\305\240\323\247?\002mG\235\036\205\253\2771\340\347\324\343h\310?\311\337\362\274\365\006\323\277\376\262Iy\305P\262?V\342\246.\001b\265?\'3`\276\305\367\233?\3526\321{\217gr\277\265>\014G\211\303\257?\227\365\326\025\267C\276?\204=o\364/9\302?\030\013\372\346\301:\222\277\031\237\'\001\255\252\203?e\007\264\204\342\033\273?X\227\221\243\236>\271\277\3162;7j\236\261?\364\335\024\206\307\r\253\277;\277[\226\362\016\234\277\363\322\370\355Us\266?\014:\256\256\023\241\266\277l\244i\235b+\300?\266\221\"T\266\020\264?\032\277\\\263L)\201\277{\230\240\253\345\202\030\277_\211P\031\236_\313?*7\213a\364\300c\277V\344\376\007$R\275\277-\237\022\220\213\276\276\277\240z>M\036\320\261?\305.\373\006M\305k\277\324ed\233B\340\254?\204\036\345\211\217\261\222\277\2654\265\005\n\322\235\277\273\310,V\312\334\276\277?J\304\"ms\240\277e<\027z\025\351\234\277\372\277\242\367\350\325\234?I\026=\t]\213\271?\215\307\344\222\037\371\311?\362`m]\351\251\273?\013\233{k\360\332\303\277\221\354\264\353v\374\300\277lVg-Q\354\216\277\n\253Y&\363\204\245?#\313\353\016@\203\266?\026\254\311\335\322\203\236?v\234E\256\326m\225\277\246\244\020I\207\234\254?\212\212/\367\363L\263\277^\013\217\257\361E\266\277\354+Vp\330\013\211\277 \216\375[/\007\250\277t\336Ek\021v\255\277\232\324\r\203~\353\263\277^\332YSn\205\257\277\302B\261Q\014\003\313\277\031\016\311\017\230~\330?\374\252\334\335\034\350\225?S\235\230\366H\233\263\277D\326\r&{\340\262\277\301\206c\230\203b\305??\331\366\016\030\'\253\277\231N_\333Y\264\241?D\027E\320\300\301\302?v&\337\334\235\324\243?\211\324G\304\353z\263?\374\306\327ol\353\316\277\251\244\370\372\265d\262?\177\010|\321\273\250\266\277|\262\223\313\024\366\265?\335Mx\r\030\354\247?w\237\317\315\220\305\260\277\371\262\023\307{=\274\277C\337\021E\r\032\261?\250Xb\232\332\352\246?\255\352\226*\021U\250?\364\020\032\312x\326\304?\335\261y!1\255u?C\304&\313\020\346\212\277X\242L\214?t\243?\2416\017q8\001\204\277\314\032R\312\0069\261?\022\360\337/\0022\244\277\213\006,.\366\237\264\277\211\274\326\300\"\255\303\277\217\332\226\362\206\304\262?\370\333\215\212\371\317\302\277\207mJ\232\001\024\230?\316\344\315\341+\200\247?P\t\270R:.\260\277kR\263\037\211\031\220?\306bX\303\336+\220\277(\325\000N,o\235\277\323:\025\224\363\233\260?\022\304G\263\3733\321?e\037_\371\315\216\214\277o\237\2375\027\314\211\277\031\237\000\025P\360\324?\321\351\242d\237\346\266\277\356\363\007\273\3272\236?\004(\007\2518\245\213\277\207\2243\263\353\307\303?\240T\020!\377Q\232?M\312\032\313\214\262\272?]PE\036QW\307?\003%\244\307\2739\274?\251ir 5\353\263?\022\003\201\243&D\265\277\233H\223\377-\317\277?U\372\252[Y\244\273?`\261\237~v\253\266\277\276\031g\260\017\272\230\277\355\356\345\334\337\301\265?\250b\223\253\357\262)\277\324\341*\005:\t\251?D\341(j&q\315\277\271\300\303\220A\262\234?.\316+~\317x\255?\265\031\2663\356\357\260\277\263\366y_E\207\242?u\030<J\240\345X?\273O\216Q\252P\270?\277\333\226\017}b\277? $\213\323>\300\302\277g\303u\352\336\031\274\277\373\226\216\255o\356\252\277i:\262\335j\341\275?\251\004n%\255\r\226?\347\363\034\323\000\t\301?\333\027\'B13\262?\005\370J\031\276%\300\277s8K$\233\210\234?\003s\352\264\177\275\256?\031\367\267E#`\260\277^\357s\341\217\251\255\277\362\262\203#\257\354\227\2778@\347\317M\025\265?\263\2748\250\0239\277?\\\276E\032\375\362\257?B\234\235\361:\215\330\277m\372\023\236\316e\264\277S\232G;G\222\275?\202\240\363Z\302\301\225?m\343\203\022j:\277\277Xm\034\013\026\251}\277k0\270(\246\265\263?\275\205!]\261\366\264?\361\210 \324F\261\307?\336Z\242\336\310W\263\277\372\210F\332\214x\005?\207,\313&\226\314\242\277\"m\356d\266X\300\277c\261\033=A\024\237?\243\334b2\223_\207?y\202\032\3038 \262?\216\300\306Qr\245\262?\023\324\213\220D\263\317\277\235\210\272\207\350\215\261?\030\3329\210\355\322\221\277:\347\037\304\345!\261\277\231\264U+\301\353\200\277\257\323\335W\377f\270?Y%\271\327^\212\223\277x\017\343y4\363\224\277\331C\223\'\360~\245?@/<O*\317\274\277p\020\326L^\225\250?\026?\336VX\320\245\277\270\331\343\3724\007\267\277`\007b\260\355\336\241\277Q2?\371\261%\305?\207)\340\265 \341\270\277\352\375\031\2209\226\314?\025\301[\006\362\246\261\277\221_\266\33621\216\277J\341\366s\367\007\316?\273\rfR\366,\260?\374\303\'c\3625\245\277>\314\230T\254\200\303?\322\345\331\227\036\006\306\277nS\243\340\260\226\223\277o\246\372\177\313\315\246\277+\247\3527\207r\300\277\217\311\310\213\204\371\244?m\321\021u\266v\304?{\n\004\016\200\311\227?\023\265\204\016\tD\320\277\221E\370\0056\204\224?[\321X\332QU\267?\212ug\304\267[\267\277\234s\003\371t\"\253?\nr\315\231\252\221\301\277\022\372\014Lxi\241?\312\250\003\036m\007\305\277\325\242\332\023\tg\322?\271\0177UH+\240?\207U\246\277\376y\321\277\211Sl\364\212=\267?z\211\364\227CL\226\277\262[4F\265k\263?`\335\211\341w\375\262\277\220^t)\245\272\250?\3769\014\003\246\036\303\277\270\006\035/=^\222\277\200B#W?f\242\2778\267\3532\2073\262?\262y*\254 \314\303?\233\326\310\317\332\364q\277\347\034\212\213O\027@\277\260\025o\304\266\336\254\277=\265\216\025\214\225\307\277\016\266\027(s\346\276?W)!Is\230\253\277\242?>\355T\207\246\277\211\030\"\005\366u\266\277\027 \243.O\347\265?\230\272\256\023\007q\234\277!.\002\344\004\251\307\277\201\217F\2731\237\230\277\276r\022@\235:\254?\222\262\2447\023\336\300\277\312\206\342\322\007K\261?;\354\272\242AI\276\277\215\250\242\txi\270?\330<\271\340<\373\306?\375t`\245+\301\272?\355\337Ht}\325\257?\206\002U!\350&\275\277\2629\010\316\365O\242\277\376\272\'.C}\240\277\374\300\206\236\225\363\202\277\372((\240\363H\300\277i\352\336LFv\260?%))\312\225\300\302?\231h\277;e\261\227\277\3620\n\310\320\354\216\277g\016`\375i\313\222\277\366L\213\233\231\022\262?\257\rbg\303\021\311?h\210+\303\212\024\261?,+\274\263\310\"\302?\272\214\344\203I\366\311\277.t\276\271\303\023\241?\201\205\366\332\214\306\320\277\001\232e\013+)\226\277rUo\272\263\371\261\277\022\310\321\026i\035\262?Fl%\017\0063\277\277\252\350fc\252\365\242?/\005\316\036\361\214\227\277\312\243p\204\030\243\242?\200,E\373\215%\300?\261\244\217\232o\226\330\277\3362\'h\257\231\226?\323\276\251T\344\345\237\277\316\314\342\335\001\321\256?\251I!\'7\267\263?\351.:\340\317\304~\277\325F\222\232:T\253?-\016X\302\n\236\241\277^#\030\327!}\245\277\240\334\226\227\361Z\304\277\353dT\306=\300\307?\315\305\205\034\342\332\300?\272(\220\365\001/\241\277\35421\342\307\003\303?~K\231\200\313\372\310\277\325\201O\303\270\227\217?iW\177\274J\246\232??\372W\243Qr\272\277\177\276\025\031\233\221\253?\223v$\376(\033\257\277Y/$\370\226\030\205?G\n\303$\267\304\274?\237\240\262\014\224\230\266?\'`\237\245\324\027\243?\2757\225\333\213\364\257?\237\346\021\277\221\017\205\277\266\260\226\365\311\335\245\277zQ!\236J\262\321?\240yL\002\336t\225\277\303@\364\302\334(\253\277\0253p\204|\376w?\305\234\337\213J\310\274?\335:(/\330\361\224?{~e\207\304\255\245?\201\325\371\256#\217\266\277\202\334\302O\rN\240\277=l\3656\345\373\260?Q\210\372\343\3068\270\277\003\202\311\213o\250\263?\020\227\003\004q\332\277\2773\034F\274\302\204\253\277:J\327\226\351\027\316?\275\262\220S}\362\261?\311{6\277\212\242\250\277\233M\200\235\341\000\257?\340BR\377\252\032\275\277\225yoy\253\177\257?u)\272\347g\316\305\277c5\302\224\233\353\245?<\214\002|O\277\235\277\032i\252\355\312e\240?i\036\245*\325\202\240?\372d\233\2466\303\302\277\354\022\254\006V9f\277\310\241\376\242X\021\277\277\326N\341\300\256\335\263?\342=+\r\361;\322?\205\366\210\215\370U\270?\202\254H\270\t\315\246\277z>\344\202\313=\300\277s\365\232\225\316n\310?z!\323\334\352&\227?\014\367\277\005``\261\277\214\201\234\223\262\037\274?i7x\371m\306\215\277|\347\350\374\300+\256?\366\007\202\252\013\263\303?\316w\357\242\253\312\260?Q\006\003Q2\265\273\277/\003j\301\201c\246?\340\007\342y\222\344\247?$\215\233\351\221\277\277?3\\\333\020\302(\276\277\300\301\236\237\2558\264\277j\230\257\3645v\257\277&X\203\217C\313\233?|\231\014V\267\364\300\277f\262\006V\214\301\310?)h\266\306\367.n\277\372)\217\024\276\343\264\277\302n\372\200\215\315\262\277Hl=t\035\315\264?z\367[\033\331\303\302?\364\037\210\005\231\204\266?\317\251\354\203\335\221\326\277\331\024>\010\313\036k?4\344\320\344\304O\271\277h+1\220r\\\252?\237\377\313\0136\270\264\277\004\326(]H\206\255\277^~_\002\312\270\257\277\275\010\352\r\235*\215\277\326\207\317\021I\224\273\277\005\026C\2255y\265?u\005\201\230Mb\300\277/Xh\273b\231\246\277wa\007\353\254\031\270?\034\300\023\260R\235\261?\353\212\342\251\241\371\245\277\234u\200{bN\301\277l?Jj\361m\220?\350\310\016\026\261\271\310\2775\033\323i\200y\263?\223\325\032\361D\236\244\277\305wF\224r9\274?U\010k\262\013\311\307\277\353\006\377\036\361\203H?\333\331g\034H)\303\277\004:\315\255\034\363\237?i&f\265\353\222\274\277\377/*3\016\322\237?\367\374\2639\334\246\312\277\030\363\ntC\013\224?/7c\303\027\321\223?:m\211\313|2\250\277\227\036i7\264\013\206?x\261\244\2609\265\222?\261\340w,\252^\272\277\032[\261\303\254\227\224?\001\275\372\354\313\257\240\277\236\230>\3036\333\246\277yQ\302\020\020\373\243?\347\301\267\236+\323\312\277\tD\023\2168\255\243?Le\262\376\232\032\206\277\010\202j\262\370\014\245\277N\273\244@4B\255?n\346\204t\352\272\205\277*\263\330\246|#\223\277\370\317\215\264\032 \275?\250\271\375L)\302\216?\227n\243{\325\341\263?\361\321\251<\222\235\302\277\022\036-\005\201\303\237?\\\345\020\234\326\220\\\277\013\220\252K0\375\260?i\275\263B\362\350\271?X\323\212\003T\203\246?m\"u\250\345(\326?\007\347T\017\n\005\320\277\220a\0345\300r\263?\316\235\010\214\263_\277\277\014\026\377\002\247\337\300\277\032mG\373\342\000\301\277\027\001\031\366\214\207\243\277\355\302\2251\314g\264?#PX,(6\207\277]\016Rf\036d\237\277Y\007+\303[j\250?i\256>@uB\263\277\250d!\377\032Q\215\277\200\032a*\025V\301? \256\364\254\356\032z?:/\223n\370g\214?\355c \247\037\032\235?\272\267e\341\304\250\233?\301\204\354\321\334\"\257?\200\316D\r\255Yy?l\202=\366C.\302\277\014\t\033p\275\227\251\277;qg/\236,\300\277BQ~\202v\276\206?-P\367\2459\364\272\277\254\024\307\373\203\343r?\210\004\036\\\370\302h\277\\H\332{\251\350\256?\n\264\212\022\342W\224?\246\352\261=8\272\313?U\314k\214w\204\262?]\236\272\r\024\361_\277\326s\227\262\005\005\256?\247\262\354\014\307\214\264?\007\372\t;\305R\263?1I\204\013\243\345\245\277\360\t\3251\032\177\302\277)\231O(N\034\304\277W\024\t4A\332P?\246\"\217\352\354^\275\277I\324\251\300\366\305\321\277\255@\265\re\305\261?\203\004{\311\262C\230\2776\363f\271\225A\301?x\027\317[\345\274\320?\236H\221\005\245\255\264\277\315\370\235\350\340\235\307?Kx\353\2626a\243?\264\373\276\200E\255\302\277\027FuOH\010\270\277\372\307\0331\263X\275?$\220A\020D\214\266\277\\\006\026\265P\350\260?\305\357g\0367\301\264\277\300\245\233}S\233\276?\374\273BoyK\220?\020\r\236\020]9\260\277\271!\232\355\014\371\302?4[\216\260\230\006\271\277\252XP\030\220\321\255\277\020\\}t\226C\233\277p\2347!I\210\323?\303\263\'h\030O\236?\027\243T\224\204aq\277\210\020\224\216\363\232\300?\006\023P*^\r\204\277AG;\373\275m\243\277\272u\301\210MP\232?\006>\035\254\271\343\270\277\243\362\013\2531O\253\277$?\311,O\006\271\277\362\371n\005\377_\301\277\310\306U\311|Z\312\277\214\304%\006\316h\307?\364\245\337\027\216B\256\277\006\307\376\272\303k\226\277p%9d\236\311\302\277\357\330\226\325e7\300\277\263\202\271DK\332\217?|\215\235\302[*p?R&\323\355\264#\277\277N\363v[\020\343\240?%\03593\021\351\242?\261Aa\260z\334\227?!@qY\2440\275\277\"\360\226\tX{\262\277\200\360_*\3263\257?\\\273\014\360\3674\252\277\342\275#\021\023U\245?\030\266\313L\264\321\304?\214\211q\262O\211\273?\262\337\367\004/\333\260?\216\253^\"\024\034\313?|\267\343\232\373\347\227?\376]\255\377\360@\241\277\211L .\240H\264\277\315!\307\022\303V\246?$\277\326\240\235\222\215?Uaa\331Ce\222\277\3569\000\234\237\334\265?\224\234\340U]\363\322\277C_z\202A\320\225\277\204\210P2\312\315\223\277\013TA/\253\372\261\277\265\007)&W\023\271?`\260i\323\244\365\261\277\311\266\221V)@\300?7\360\014vw\263\244?$\326\304E-\215\255?\220\nX\361\373,\242?\205\004`\340e\n\302?\\6\'\266a,\213\277\300\271\364\310\003J\206\277\000\236\021\332\211\210\277\277\247\333\245j\r\271\320\277\372:\307i\203 \224?\254;_\211\312\034\303?\366g\3137\222zz\277u\333\020\216\367\370\252?\302(\32279\270\303\277\177\375op_\026\307\277\341l\010W\'\267\302?\n\250d1f1\301?\251\252\222s\004i\300\277\262S\227\361\377B\305?\201\233^\355\357v\241?*\331\314\352\270\341\304?s\245\264k\333\337\313\277\016\2253E\271\372\300\277v\027\347L\222\206\236?l\353\016\226\335p\260?Y\335\241\210\235\316\265\277\0073\023\374fB\224\277\377\006<l\036\246\303\277(\366\244\031^]\250?\006\343\366y\236Z\271\277\332\215[\202\344\315\226?\354\312\275c\307c\261\277\223\240\264\t\255\263\320\277\210Q\307hd\243\266\277/\372iQ{\234\240?\376\375x\032pN\277?\225\263\027k\177\202\243\277\334\341\277m.\266\266?\005jc\020\244X\251\277\242\314F:\351\373\303\277\355J\037\202\016\346\272?\352\263\307c\327\004\255?\t6\366\320\210\201f?\306mho2T\310\277m\2228\237@\305\264?\003\201\270U`\3651\277\355\203\031B\"\233\260?\033\340$\332\332$r?.\032\336\256\342[\254?y\276D/\353\360\320\277i3\255\3151P\267?q\335\235\274\010\226\241\277i\273\211\016\001[\266?\311\337\372\0224U\264\277\237\336}\034\272K\256\277z\357I\320v\252\225\277\346\316\200\333\237\013\264?\257\261R\221\227\367\247\277U.]\000j\242\207\277\301\212[\002@\331\214?\016\272Y\375Y\025\303?\266\245{G\324\212\263?\252d\342\241+\232\312\277u\032`\016\312/\300?\314a4Eq\016\220?\233\362hm\034\010n?\273\347\315\306\021\354\223?\251\237\313\206C\235\257?\212\373G\002\343\332]\277\372\025\301$\2705\211?\244^0\3463W\261\277\352t\346(J8\310?pU\321\235\016b\235?\252\312fnz\353\235\277\250\022\235}i\277\244\277\001O\355.\004\253\241\277\235\023k\306\221\205\240?\370K\254;\027\361\302\277\262\217b\363\217K\302\277\353F\214\251\302\342\276\277\025\377\322\367KA\306?5V\253\377\321\322\326?\373u\317\357\006\213\325\277\301\016\031\357?>\204?&zCX\321|\245?{6\314P\000\237\262?3\206A\351\253\002\236?\350\2145a\211\265\263\277P\205\246`\2652|\277\245\212\r\224\255^\247?R\245\027\202\023\014\306?\215\252D\313\2656\267\277\326\370j)o&\312\277=\263\270\207\006\033\253?\210\376\312\261m\347\313\277T\305N\222\333\335\300\277\222EH\327\337\372\235\277\333.\256cT\333\234?)\003)\355\241\233\224\277p\3751\201s\364\305\277\005\350m\002M&\227?\203\275\024\275\363\237\245?\357\203\004\361\2349\273?\361\213B\032\222$\300\277\375\354\321|*\245\303\277~Z{_xG\326?\321\332\225nG\345\336?g?\341\034\013\001\265?\213{\013?\014<\220\277$\353\254\247]M\253\277\274\263Y\363\032o\303\277\025\204L\034\366\371\265?\232\270\221tc\200a\277\377\362@\320\227\345\257?\360c\0329\316A\230?5\tWM\367\374\307\277\362\300\2315\233t\271\277\251\240\267\024\261\342\274?\013[\354\355\345\374\256\277x\222\001\201R\252\243\277m\333\313\257\276\323\341?\335Hv\210\035\274\241\277-{\246[/(\227?\323\361\256u\302\226\276\277\317?\306\236\t\322\277?n~G\017\303\371\273?\240\254\013Rk\341\267\277\237\030\235u\232\364\300?]\340\013\014o\222\251?Z\347\203\351\302!\226\277\254\216\005P\232\252\306?\021\026\306\024\001\035\265\277P\262\205\232\037ms\277\373\207\002\306\335_\320?\t\200X\201\353\224\327\277\0347\212\221\321\'\250?\236\ni\231\274\237\310?\345;\342\317\303\315\234?\237(\237\215\004\036\300?V\374\371\265\n\321\235\277\3660n\360\004\314\220\277p\337\316\307\240\356\246?\265\013\337\274B\363\243\277t\304\246\333i\352\262?L\003\317\240\316\326\263?\016 \n\032pj\300?\'\254\265\326\024|\217\277\303(\253\270@8\224\277\326\331\217\301s\216\264?/+EqP\363\205?S\\7>\331\241\227?\366+{UW\354\260\277%\304__\270\372\251\277\024\001\241\306\363\246\263\277\271\326s\254|h\223?\321\253(`G\342\254?\013\216*\003\3038\272?\206\352\261d\316\364\273\277:\360\371\007\236\202\273?3J\031Y\"<\260\277\2633\255\202-h{\277\277h\242u\r\305\272?e\271\302\314\251\253\315?\357r\0271\213\177\262\277\263cb\030\275\361\274?\274\001g\334c\252\310\277\323\246\332)\374Q\276\277\333\'\236\337O\317j\277U\352\'\205Ie\241\277>>\247(V\326\303\277T\366\203N\247g\260?2O/-5\000\200?AUuD\027\270\270?Z_:\344\3725\250\277C\321\n.\341\031\246?~s\252\035\303\330\301?\273%e\177|\320\306?\230\261r\224\006L\270?\323\221\255v&\376\272?2h\234\037\245\306\256\277\036\026Z\205\272r\230\277\227uy\270\332\366\303\277G\274w\222\363\332\252?\347n\350f\244\307\247?~^\310\242\351\014\256?lGlB\374\244|?7\244\341\221w\235\253\277\242\020\277L\263\034\300?vb\334\225mf\310?\213\262IME\230\307\27701\367\301\\\320\307\277j\021\330d\005\217\264?G\321\204\222\347\020\310\277G\rz\373\341\017\303\277L\213!\333O1\271?\2663\217\206\3732\265\277\320\007_>\353`p\277\236\345\355\337s\350\260?\031]q\200\364\316\263?N\274{f\314\313\257?\264\217\032\020\364;\325\277\004\250\314\355\000|\262?\374.\367\236\332\345n?\020[\353!\245\341\207?\210\224\227\014x\362\207?\371\376\231\307s\037\215?B\232\341*\tf\274?\242\251\244\r\235\020\270?\344\232\261\333\375o\251\277!W\243U~X\245\277\352\267Lf\202\226\265\277\225\347Y5\311\243\303\277\034\3032>\363\354\240?\355\333qS\365V\241\277Bt=\232\022\355\303?\261\316d\334\321.\243?\003]#\360uK\270\277\300\210\n\262\276C\275\277\003\227:\221\322\220\306?\210\260D\324\332\212\227?zV\262\372\257\013\260\277\221\033(\306\013+\300\277L\"mV\3755\224\277\031\335\215\r>\261\253\277\014\345\335,H\311\306?^^1\0366\253\265?\310\334d\271Q\337\216\277>TiG\314t\257\277\223\361\226\350\2371\240?s\014\217g\255\320\244\277\241\200E\335\3740\240?\333Oi\247V\000\267\277dh\224m\255l\203?N\300\247\364Cp\242?\352x\325\037\363|\247\277\330\330\311b}$\276\277\232\214\364\341\271\362\255\277W\232\370\315\266\323\304?\261YJ\217\022\353\272?\014;\345b\262g|\277\226\350O\252}\245\256?\010\371\255\310\023\'\274\277\360c\340\005\006L\267?\231\326\321\301\373-\244\277!\030B(&\001\262?E\323\345\350/?\240?p\022\\\231\307\336\265\277\253\027\036\215\376\355\221\277\2623Se&\265\243?;N@\312\336D\321\277\236\341+uL\344\226\277`\363\214\262>\357v?\342[\374r\2537\265?@\023\231\205\001\311\256\277\3500\365\334H\270\263?3\263\220b\004\275\222?\234\323\301{N\033\216?I\366\202\336(\226\302\277\311\370\023\264d\225\211\277qCf\350\210\353\305?\"\373\306\376\343}\312?n\273\003\321^\241\203?\331#3\004\253\263\253?\212E\342\030\214\023\261\277J\321\332I\330\t\246\277\246x\256:\007\266}\277>\351\201>\205R\247?4\220\3002\302U\322?\371\307H+k\353\255?\\\263\023\006t:\242?\020\271i\002\375\326\223?\217R\326U\023g\255\277<\276\035\215\3773\250\277\3119H\220\007 \253?\321kS\036\017F\303?A\213\035\271\270/\260\277\324&\3753\201\224\275\277E\215\315K~r\300?qv1\001\343+\324\277=\3413\376-\017\244\277\2600\371s\000\331\300?\275|\\H\352(\321?&G\001[\242`{\277\216j\2170:\362\214?\376\212;6\205\371\274?\212{\210 sk\244\277(3X\320@\010\240?\335Gpp\314\027\300\277\305>1\002=j\227?\201A\301\344\276\242\250\277\310z\346h\301\366\306?\256\210}\220\223\274\277\2772\025\210/\214\355\270?]\250S\325\'i\224\277z\273\376R\233\244\274?\317|\323\273\025\004t\277\025[S\340\316\325\202?\222\177\034\022\312\020\245?\343\267\261l\205\252\311\277\356\214z\000B\013\244?\240\213\205\206\'T\217?\215\006\312\333\321\022\271\277\333\237h\006\250\240\223?%\377>24\236\261?\305\206B\367R\204\262?\224\335\305H\223\363\303?d\322\366\311\331\267\274?D|0\020g:\230?x\241\372Y\374\265\266?\245\247\004/\245/e\277\361\331\215\3001\370\220\277\352s\226~x\241\267\277_\320\327hu\027\260\277\230\364\355\376g\322\266?\233\016\363\221\350\007\241?\264\240\222^\214M\307\277\254\367?\212\276\256\241\277\332*S{i4\237?\272\331\3143A\201\265\277\346\260C\231\365\307\300?f\262\035\210_\351\226?\306Ex\034\311\217\260?\016\272h\250G\327\243\277v i\210\276\223\276\277\367F\3025\246!~?\266kb~\002\014\305?\232\242\243\202Q7\240?[\323_tx\357\256?$^\201\000a:\241\277X\253\255\316?i\236\277}\0139\201\323\213\223\277\2271\220\252\260u\202\277\306\246\351\"\242\202\300\277T\026\230.\373\321\327?\316\243\247\327#\226\300\277\241U\330\003\311\323\240\277n\201z[\312a\241?\274\344\363\327\030\257\241?l\210\205\327\325\341\214\277\'\245\215\313\260\341\220\277p\300\224\216^u\324\277\275[\311\330\305\341\226\277\243\257\2079\236\014N\277&\211\256j\272_\272?\356\3453Z\2147\277?\361\205\354\375g\245\231?w\334?\230\305\215\210\277\244r\n<\2712\263?@\332\216?l\\\266\277\251\360\t\267\224P{\277\034D(0\252\303\241\277\327C\221>\361\214\232?\261\210\355x2\362\262\277\267M\270\330v\030\274?sv\251\267d\337\272\277\353p\344e\375\323\255?\350\024/\243\0225\245?5%A\212aa\323?\010J\253{\202\"\263\277\354v\217V\373y\257?\203\316&\034\252\323\304\277\277\204*\030\265\014\317\277vjg\025_\372\270?oQ\t\315\0131\211?\350\003M\206\251\277\272?p\377=\207\005\307\257\277n\247\250\352\244P\256?\016\305\261\342\263H\276?-uw\224\325Bn?\004\270\217Lf\025\177\277\001\224\267\007C\000\262?e\302\237\376I1\316\277BOd#DH\325?X\375\'R\213\265\211?\302\026~\273\323\334\303?%\370\200\233\3320\310\277X\004\236\203\220\341\250?c\255v\255\025n\301\277\005\273V=+\030\255\277\333);\006\016d\314?\273\3172\216K\327\250?P\3572\030\256D\214\277\246\246\373\373\206\213\252?\324b\031\034M\305\261?\253\326!\016\210@\221?\214@\264\020`)\260\277\273\215\300%+\342\334\277B\\u\0235\227\301?\207#\200\307\327\030\263\277\313\256\246Zf\247\220?}\"\252\357\216\220\264?\233\n\013T\272\027\243?&\2348\344J\311\262?\306\357\335\263][\307\277\367\364\246\032\037\223\247\277,|\007\302:1\261\277\314\350|\207\022\355\256?\023\030=r\324\203\216?\177)\351]\205\335\251\277\226p=+\312y\267?\204v\366\266\374\233\315?\224\307\370lV\021\225\277\350eD\365\316(\312\277-\201\344\367\312`\201?\217\241\364\262\023W\213?s.pS\223)\275\277^\006\367pg\263\251?]\376\"?\234\366\300\277_\"*bZ\230\211\277#F\333\"\257\362\243\277W\"P\300!\317\260\277\0050\221\030\025\354\240?dBP91\024\242\277:Q\214\275\010_\310\277\362\352\246@Y\004\327?agu&9*\277?\363\215\246r\336\272\253\277\304\236\303JpP\240?\023\353O\230\246\333\266?\246\'0E\326\033\200?\250I7Mr\210\206\2777\250M\222.B\263\277)I\213\364ks\301\277w\243H4\240Q\302?%,c\225\354\356\247?\025T\362\246\214\204\274\277\013+\267\246\247>\270\277\246\275\006\316\326\342\307\277\200\225\335\275\330\021\323?\306X\257`J)\251?\366dG\370\235\245\252?\026qTJ\035p\254\277\002\r\232l\214\036\201\277D\177\014t \374\244\277\207\002\327\177r=\265\277hq\033\205\000\352\263\277O\235;\013\371\357Z\277\222V\330M\214\336d?\016\257\216p\323\263\272\277G\247\3568\251F\243\277\375\233\232gX\324\205\277\245n\'\342\335\265\321?\203z8\354\016\032\337?v\016N\237%_\232?l\352GI\320\207\247\277oz\371\307]\001\240\277\300K\343\177P\377\240?\010\215\241\276x\354m?\342s\337\350D%\264\277\314!\277\";\221{?a\025&\202\234#\224\277\304\374\251\337\002\211\273?\325{[\262?w\266\277\3469w\231\261\375_\277\234Y\376-\211}\206\277nc-SjE\304\277\247\355~^\315\252\244\277\0233\354,\030\013\253?\370\326K\327\026\220\261?y\2703\205Z\207p\277\330\036\301\030\213\207\266?\177@\304\203e(\267?\014P\231\026\004\310\227\277\370\007\213\0358\357\310?\372\330\033\224\003\245\261\277\347Qo\303\351B\264\277\027\023F\364K\323\272?Y\374\355\350\204&\251?\210AGOp\276\226?\273\372 \241BJ\312\277\251\322\016\334Uo\271?|\030\261x\313\034\266?XF)\010\310\203\255\277-*\003\340\370\275\262\277\310un)x?\245\277\007\3700\3765J\205\277\247\257\255\204\326D\303?>\242%\244\235z\301?\274\210S\252\262;\250\277N$\361LX\360\261?\224o\r\234ar\250?\231\021\310V\237*\253?\217\023\252\306\2263\260\277\212\t\372~\352\n\304\277\032\306\216\313}\025\262?\242\177K\271\343\213\230\277f\257\247f\035\340\212\277\210\243=\273\001\024\232\277\344\253\3652\363\342\222\277\331\364m+\n\371\260\277\367\317\222\003\006\306\264\277\211!\210\271\314h\303?e\220)\371\224\n\177\277\254z4\020\226\324\303\277\371\371\274[\370\371\316?\376..\276\305\333\270\277\177\366\021\022\221\354\265?\224 j\2452\005\260\277e\307\245\0200i\274\277\257\310$\003\365\252\240?\2339\236P\230\010\273\277\334\260]\030!\237\317\277\234K\237\3009{\263?p\341\273\332\017/\237\277\366\344q\301YA\307\277\324\035\310\234\3660\302\277\2176\037\366fk\251\277\343\352&\2472\036\275?5QT\365FR\271\277\367=\207\203\232\314\245\277\200\363;G\035\313\211\277\304x\263\214QC\263\277\361\002\006\254V\"\276\277\374\362\343^>\220\253\277.\327\r\r\003\021\245?8\212\r\236\006\363\303\277\362\357\021gk`\300?\377\001\344\t\206R\300?F\213\207I\301\000\253\277\270BG\370\260\233\237\277{\020\373\002fc\303\277\014\343\251\2326G\300\277\241\203W\3525\370\275\277\216s\313\321\226Mr\277\243B\365Y\223}\265?L\337\312\375#\271\266\277(\306\017\t\273\030\222?\321\037V\331A\202\221\277\003f\330A\270\221\250\277\224\310\006U\021\373\261\277u\026/\020\017\205\256\277\033\226\323l~\331d??\307o\225\027\376\264\277o\313WqmL\267?C&R\264\206\036\200?\317\375\261\230\306\234\303?\314\354\2174\336\363\231?W\274\233\377\036\264\265\277w$\372Q\323\310\301\277\332$\232W\266\037\306?\321\256\356\226\357\331\266\277^\365\206\306\220l\223\277\025\312\\@\030\267\261\277\304b\313\312%y\223?\311\254.>g7\246\277\201O\3526\300\301\250\277\001\006\345\310\220P\251\277!\236\220z\257\332\266?Y\026\021\212\000\271\237?E\372\377/O\256\276?\207\346\346\255\366\222\265\277B\222\274\317\036\222\273\2770`\331\313\210\341\245\277\230\326\240aNw\257?\014\366\354j%0\312?p7\177\276\351\363\233\277\215\255\010Th\277\317?\017%a\320\255\375\301?\372\022bMH%\247?\221\007\300\375\316K\302\277\326\254,$Y-\276?\344\301T\336l\314\245?V@\021\005\035\222\227?\210%|D\371W\317?\246j4\247\022\273\301?S\020!F\025\213\270?\035\032\331\233\336\256\274\277\205\276\377\027\336\315\314?\027\227\265]\0374\320\277\216}lyY6\256\277\225+\240d\213\222\272?\366\247\203w\205(\267\277\224\262\206\206\275r\236?\373\355\007\017\254\253\217?q\255\327\231\316\342\260\277\263?!\n\022g\220?\363\3452\365\377\235\274?\340\344\272\317+q\313\277\370\264\244O\023\333\262\277\270^\221&P\300\247\277{\002\355\326\212\003\271?\322p\260\337\236\374\266?\362H!8`a\276\277!\345\022^>e\251\277q\010\261\272\0107\300?\200Ls\003i-\255?\265\211\303\013\342\265\220\277q\334.&\254\277\265\2775@\231\372\307*\251\277\035\241\264Ws_\211?\346\327.\233\n\357\260?u?,A\341\032\271?\356\236\307\r\003|\247?\3032K\352\002D\247\277\253f1\\cx\261\277\256\010\266\340\346]\263?1\361%\222H\t\264?\237\231\373;]\376\252\277\026c \206\340\340\245?\373\227\313B\r\303\262\277$n\335v\265\020\204\277\202\262\330\215\277\"\254?\211\337 !\305\203\242?\377 ?\252\330\237\310?4s\240\313Y\214\246?]\"\367\360\323\305\222\277D\030mS\356\"\275?\305w\200%x\260\250\277\216m;\034\340F\254\277\343|i\025\036;\237?\303\225u\316\320\275\300\277\036\333\342M?\323\217\277n\260\031\346:j\301\277_y\276\3107_x?;\020\313\314\221\036\271\277\324\343H4N\244\271?jn\024\270\267\245\231\277Dh \215\036z\305\277\252hM\321\0026\260\277-~A\226\013\313\212\277k\246kB\353(\306?\352\217\034\307U\372\226?\237\360\235>2\326\226?\231\306\tk\344\014\304\277\316\367\333\373#%\275\277L8\346\202J\327~?\231KK\272\232I\301\277\275\314\317\036\276\312\301?\225\217\014\020\256\030\226\2771\224\006\206B\350\305?\363:\314\241\272\222\242?\221L\355T\014^\303?\034\3646\277Wl\203?4\320\211^\3069\271?E\214R\303f\343\261?\007\2065\335\247\321\215?\330\017@\360\3040\242\2778\034!\221w\025\226\277\021\327\335\322R>\323?\217\267k^>\231\264\277!\204&\221\312\203\266\277\234jq\250h\316J\2777\276~\237\"_\312\277\361c\'}j\336\237?\310`\306\n\3135\241\277{\244\315\350\241\271\216?\026=\030\304\324\001C?\370!\201\"\r3\275?.Z\277\307\033\203\272\277\206\217\277u&\320\225\277!\247\357\375&s\260?\245\250\314\2573\002\310?\272\025\346\302\360\260\200\277\353U\327\036\016O\265?\256E\305\302\010~\305\277\312\332\313\n\222\013\261?\341g>\245yD\306?x!\365\020\363\373\261?\352\t\013e\322}\256\277\243\316P>\206W\256\277tJ\222\202\027,j\277?\360\266\262_\212\274\277\3400\2564\300\365\275\277\023\254P\372L\016\242\277\311\261\345\351 \273\260? k`\003UY\244?\321\257\354\311\245\350\241\277\n\315\312\3314\373\263?]\254\221\273\370V\214\277z\315\n\226>,\255??Y\232\327\211.\212\277\244\243|D\235\231\210\277\017\201Q\214Z\332\260\277QO\252\346q\213\262\277;#\317\205\336\017\254?\347P\215!\264\246\273?\274\325\221\232M#\304\277cP\010\270\333\234\225?\000R]{:\213\300?\367\222y\234f\342\273\277\333\3517\315\222\256p\277\220\204\217\217ea\242?\301J`j\340\031\226\277\271\257\230\221\321\217\257\277}\203\264o\363\026\220?\273\215\374\206O`\264\277\316lG\367\313\236\261\277\254\032\212/\340\305v\277\007\223\330\376H\277\267\277c\274\\H/k\264\277\374Am\367\233x\274\277\336)\241\3055NB?M\346H5\025S\274?\345\321\357 (\010\257?\367}\277\027\330\353\273\277\325\232\346\024\271\343\257\277f\030V\343Z\003\237?UWX\022k\014\307?\275\330L\350>\303\257\277DZ\031m^\361\244\277\3551\331\306v\244\217\277\234\005Zi\320\366\220\277$\331\240\372\242\023|\277F^\324\210\246-\260\277-\321\325T\374\020\266?\264q\317\370oU\300?K\016\tW*n\276\277\364\312\347\322\314R\244\277\341\211\037j\315\230\234\277\274\223wj#\334\267?\261C:_K\t\314\277\257\356\360(\242\345\267?\207\004\240\032t\272\273?\220\037\341G\276S\265?\0173w\247\363\303\216?9\237\242\025\372\362\304?\177\tO,\307\374\250\277\242\013\264\24560l\277\321\261t\005\364$\247?\256\031k%\312\323\267\277\266N~\272\034\372\223\277xh\215\360\375\220\305\277\274T\267f\237>\306?k\014\364\335\241]\262\277\212b\013O\251\223\251?\206AL\265%\247\305?\271\0252\247\370\311\313\277\006\2471g\260\370\266\277\242\2468~\345\177\265\277\311x\223o\330\271\257?E\016U\376\026\312@?arU\330B\346\216\277\333\003a\203i\371\201?\205\346\215\332\017\343\265\277\0327\224(\037\252\313\277u\305.\232kF\307?\264\027n\332\211\231\331?r-s(\376\334\266\277\300\201Jv\354\034\274?z\345\236L\365\325\231?`\226@\246\312\025\231\277\265+\302\212\355\262\233?\260\371\013\020\037\316\312\277|\310(I\341_\276?\315^Z\2437\033\244\277\304\351}&`\010\302?Q\004|8\216V\267\2770\324\334\203\351\254\261\277\312\222\002\325\346\245\265?n\340i3\007{\226?\245\263\335\314\003(\250?\320pNr\303\377\211\277\377\240\342\030\341\000\251\277\234\205\\\226\340\204\265?B\360\207\364\010\345\220?\374M+\222C\020\265?vN\275h\263.\213\277\325\271\371\n\267t\232?\tl\216Ir\254\240?%\306x\232\217\320\275?\277\237\245\212&Y\274?\215\307\250\342\020Ts?\310\323x\033b2\300\277Dpv\315\313\253\222?\314:>\23755\266\277\037\231`\331\275\021\242\277\205r\302\370:\327\272?bZ\323\257zB\236?c\364@\231T<\265\277&A\261\272w\023\313\277\377T\245\254\000\257\213\277\344XO\360\362@\265\277*\033\013Y;\242\236\277\013\261\262u\032v\276\277\376\332\331|\027\366\264?\2257o\244\365\366\225\277\244\2640 ,\204\277?q\346\264\036\317?\302\277,\3314(l^\326\277ut\243#\374\364\263\277\006:o\022\216\006\300\277\316 v\232\t\316\311?X\013\301&\363!\267?\n+\366x\"X\237?\0316W\271\213\260\270?\235b\301y\323\217\273?(\002\316\265\352\252\204\277\215\272\236\206{\216\216?\000c\253\026\020A\322\277w\"\204\270\024K\275\277\261&\250\274\375\252\211\277\366\214\000\226\234\\\277?\025\036\236\354=[\317?\206\007\342{\333k\275\277\353|\304\340\214\244\306\277U\231\244\266[\330\322\277\206-\363w\221*\266?\336Y\266\322K\177\306\277_\207\234\253\033-\261?\302\322Y/\016\355\311?L\262\364Md\204\237\277\300\347\225\243\305\201\302?\007\261\255\027_\341\271?\213\327\0319\"\023\265?aT\3548\004\034\265\277v\327\005\346\371\352\306?\315\005]*\261_\212\277\210L\000\274\346\243\234?\245PCZ\205,\262?\037\t\2527\2313\325\277\223\024\333{\211\257\266?1\244\327\010\2401\302?A\212\365\371\035\221\212?D\265\302Rw\030\255?\255QT\030\213Z\260\277/\355{\260m\223\266\277\373\177\2162\325\315\304?\001\024\217a\3405\255?\245]\236|\263\333\246\277\220\374\004\205\247\035\233\277H\370\347\364\334I\320\2777\242Y\2165\260\273\277\210\276\026\216\254\263\251\277\334\n\211p\206Gu?]o\"\234\035G\210?\230C\265)\274\013\302\277\247\332\250\324\225.\302?Z\251\213\312\3328\235?\306\311i}P\235\250?\027%\000m\254J\223\277M\211\240\377F[s?\342\221\026\327\304\350\310\277\365\021\224\241\361\360\300?;V\270\345mM\243?d\311\372\206\355\226\300?;\342\353`\272J\223\277S\006\304\363\374\275\255\277t\326RTSY\313\277\243\231\211\351H\220\255?^=\255\216gU\230?\007_\202\034\311X\255\277JYl\330\266 \241?m\376\367\362\233\034\274?7\205\250,\250\226\220\277\000+\004l\357\251\236\277C\253\242O\n\030\277\277v\232w\212B\017\270?Q\331\321\237\207\222\320\277R\034\177b\213b\302\277\333\246\020\370\002\261\243?\251\020\344\235|\320\301\277\302\006\021\020\3721\242\277\231\330J\347\200J\261?\330\317a\243O\321\265\277>\003\"Y\300;\264\277p\017\320\020&\214\311\277Og\377E\377\335\234\277\313\177\033\222\340J\235?\211\277\t\005\231\372\313\2771`\n\336\207\272\262?\376i\2574\006\200\271?\252\242\241}\005\225\312\277\3602\370dg\327\253\277\276\367\n\364\376y\257?\302x\371WV7[?\345\224\331\3374\315\254?^|U\006\223\343\226\277LP\006\240A\'\246\277\202\334J\370kU\266\277\311\326c:\343\250\304\277\247I\247%\3077o?\277*\301,\303>\253\277\001}\375LIG\261\277\217\020\244\260Vn\274\277a\263\244\252-\265\254?:YY\017\177\372\264\277\366P\327\363\252\337\246\277\215!N4\343\333\205?\241\235\025\303d\r~?\360\252?[\327t\241?\271\212S\361\001\326\270?7LS*\357%\245\277\247\343e\243\2634\246\277<\002\326\0107\213\316\277\3727aqQ\236\242\277\037\030\327/bZ\213?T\265+=\3264\236\277OB\032r0\201\262\277\222;Ms\267\250\302?\201\324Y\216\313\330\264?\324\nZ\371\001\345\274?lz\253\202\345\342\251\277n\241\256\005\225\361\302\277)\221q\236\231\376\261\277\213\237\010%\010\303\272?\027\271/m\2042\261?\t4\261I\362a\260\277\366\217\244\371*\342\313?\243\342:W\227\007\256?\004\331\367\3729\226\244?P\200N\222w\342\320?CL\363Fuk\265?\366\273lc\274#\247\277\024\371\316\334\265a\216\277B=U\020j\007\244\277\346\001;\007\245.\262\2775\352\324\2230\021\222\277\374|\324\254P\354\315\277o\333\365\202/d\267?\206\221\255\360\327\361\251?\347\275\343?\3249\252\277wN\376\233\345\314\254\277h\255\263\202\213\236\301\277\313\223G\363\241\322v\277~G\263\315\010\242\275?\232E`{.\325\260\277;\341\356F\240\204\227?\037\276c!\'\321\200\277\237\204\013^\304\312\300?\341\241`\215C\250\264\277Q(\277\372\t\227\300\277v\266\374\216\351\223\205\277\223\212D\236\3517P?v\"\205O\307\301\273?\340\212\272\231\362\317\256\277\246\351]q\317\322\263?\270\261\2100.\220\260\277M\347\276\242\217\275\224?\027\263\273\233r#\320?r\275\265*\210\302\304\277n\357\277\340\201\260\240\277q\233\331V\017\026\252\277u\371\257V\360\361\316?5O\263g\226\333\256?x\315\021\2122\374\223\277\260)\0337\3309\313\277\202\303\300\270d\203a\277BSU?\237\221\300?\035\313E\016\233\235\244\277q\010\344!\274\243\261?\372\344\301;]\007\223\277l;+\360\001\353\241\277b\345\204\300\232k\220\277\023:\323\311p\033\270\277\351\001\324Z\204\260\251\277$\247\352\261>Y\307?aLL\214\325\354\242\277\341^C\276\370\371\244\277\341\373\350\301a\227\245?Mj\213\266\274\310\256?\207j\204\030\312D\212\277\025d\205\320t~\240?\345\347\265p\377$\266\277\250\356\027\363\342\265\301\277\370\013\347\365g\n\267\277\200\231J\0275l\246?:\372\310\332\361\274\275\277\267\363by\226\007\233?\311\245\322\254z\340\272?\261\r\204/\315c\303?\263\324z\364\241_\264\277Y-eZN/X?\313\377\\\034\355}\257\277\320\376\340\320\224\255\302\277;\223s0/(\271?\244b\025 $\344\260?\315\222\350\335V)\234?\331\\\312\002\365\261\301?\332\302\016\252K\251\227?\355\226/\224\355\027\221?W?\005\276bM\274\277O\313_\245\024\016\266?\r\3302\026\264\344\314\277\2441\366\n\006\211`\277\347]\377\244e\320\317?\246l\223\317\355\223\263?\322\247\241\224\025\037\242?\345\264\177\271`\363\214?\253\"\252\242\263\023\271?Q\031\361\204!o\232\277T\021\341\342h\206\275\277%t|\341D\370\265?K\340\355\3460\316\203\277\217\211I\366h\206\271\277\366\313\372,\2222\303?\350\266\010\2324i\267?e\251\225\014n\214\311\277kj\020%4\177\327?l\334\014\236\177\262\242\277\227l\367\261\006\207\260\277\223#\330\262-A\301?\205K$\223\005}\226?\007\"\016\014\371%\303?f\255.\253\375\272\263\277\341\372\266\301|i\206?\334\224w}\353\324\301\277t\322\226SL\355\226\277+\207\013EN_\226?Q\026\261\r\271\373\322?\240\2239\220+\365\250?t\350C\350\300\236\252?\000)-\272\275\367\242\277\246\305\361\200O\002\306\277]\343\331Y\274\210\242?\275\306\332JO`\300\277\3041Z\363x\357\253\277\277\254\337\344\020D\267\277C\357\323\256\201\334\227\277i\210\211&+\363\267\277\246U=\217\321\267\237?B^\350\216\\\217\257?\230d\347Ps!\202?b\217\361\253\013p\227?q~\204\345s\371\203\277sw\353\201\366\256\221?\363\254i\367\366R\270\277W\210\251\277\035G\217?\227\n\235\\\207]q?\343 1o4\336\302?\350(\371B\027r\274\277\246C\t\327F\305\206\277j\032\035\226\3163\247\277\310h\361\217e\224\300?\316&\305\270\305\203|?;\253\253\231\312\241\222?<Dw\250]\227\260?\t\017\027\037\313K\271?\310h\006:\302i\247?\021\376\242\223)2\261\277\237\213\243\177\273\302\307?\317\\S\341\332\312\315\277pt\025\3415:\235?8\366\262+\252\232\215?4ec\327\014K\247?\340\377\247\364%\246\257?7\304\301\225Q\364\246?VSU]U\364\310?I9\341\262\253\240\226?\024\007sd[\203\261?Z\231$\031\266\030\262\277S\3118\242\331\377\266\277\325N{\024\001\335\267?LO\332\002g\214\242\277\251\037\'W\337\217\261?L\024\305X\252\220\\\277\316\"\342%\272W\227?\r\301H\215\346L~\277\016\370k\000\033\021\274?b\327\265\\\344\334\257\2778\254\317\375&\220\261\277\266{\336H\232\010\310\277\243\300\267$\020N\305?R\257Yw\355;\243\277\235>\352B#IP\277\023\2467\344T \272\277\257|\010{Ql\274?\371\335\330\376\236\372\224?\000\375\375\032\025r\251?\276-}\232\356\373\306\277/@C\323\336\036{\2770\325\366\2422!\266?e]\254\010Lh\254\277\033\305\331\275e\n\204?\363N\271\300\370\365\232\277\252o7\223\352\242\306?\317\224{w\251\373\243?N0$\302\232\205\271\277\323\315H\036\325\224v?\010m\024T\315\023\234\277\206I\275\003\357\214\274?u{|y\013P\311\277\364Q\303\370c\220\260\277^\261\022\203\r\274\322?\345\257\272\006\310\241\261?c\207_\205,\360\260?\346\314\220\343\242\245\235?\263\n\2245Rc\251\277ag\325-8_\253?&?^\374\213\306\255\277H?}\376t,\265\277\022\200q\242\"\340\275\2776y\340\244\032\367\232\277\331HcE\251\315\247\277\274e\315\264\313\016X?\246\327\001\362\333\304\235\277\377\365!\301\363\301\234?@u\371\\\'\004\324?\230\240\005\032\277I\205\277)\263\252qJ\016\267\277\245\014\232)\013\327\267?\332i\367\313\255\024\267\277S\372\036\017\032)\254?\026\304\r\200\366n\250?\240\310$\336A\341\263\277\201\204\0359-\270\265?\016?\324E\353=\204\277!\270\006\300v7\321\277^P\034\244\020k\264?\203\225\2178eq\247?\366\275\0367\307p\307?wo%[\230\312\247?\221\267bg\005P\257?\331\276>\031C\346\263?C\354\353E\2721\302\277\333\266\013\320\371T\267\277\227\236\230\3700\251\256?\302m\"2d\315\221?\252\331L)\302\322\310\277\235\337\362\375\251M\272?s\315\344\360cV\227\277\313\223\362\236J&r\277\277/\334\200\205\315\231\277\367\244&\254\224\376\220\277z\272.\360\r\003\272?\013\243N\247\303\272\271?ll + \261\235\277\244\330\026z\256\022\215?\032D\235\356(\261\300\277Z\t\031\007\025\022\245?\177\313\024(c\266\207\277\307\021\202C\001.\263?\361:\310\305l9\230\277m\200s\350\260\255\240?\322\236\225\232\325\363^\277\2743i$\217\213\306\277\201`\345\032fZ\305?C\322\230\343\330E\251?\257\034\266\263>\244\304?\226)O\002\274\320\304\277>\"X\262 v\240?Q\216\367N\0254O\277\353\3723v\316\210\300?\216}v\327\177\022\275?\337>\344>\336\354\234\277\037\353\261 \234\250\266\277,\260\216\2109\241\250\277x\212\362O\254\363l\277\0336\261\342\037\323\240?\203\3745\243\361(r\277\t\200\321\221\327\212\255\277of\026\350oZ\265\277\202\356\013?\343c\305?7\220\260\316\353\327\267?\257\313\025-\304\020\267?\255t(+\007\305\260?\275h\243[U\310\272\277S\365\030\352?Y\302?\263v\262\301\225)\250?\301\346\210\001\300\262\310\277\262\rE\362\264\377\325\277\034\3532\231\315\243\242?{\033\027\366\255\360\240\277\t^\366(=\215\264\277o\251\246\374\256\206\320\277F/\001\303\307\272\322?*\243W\343\242\350\247?`\020W\365\376\230\304?\021\375\266\354(\220\267\277\322\274\347\254u\356\261\277\374\306U\217{)\320\277\262\372\260q\201=\313\277\264/~\267\212{\272\277\256%\312F\210\336\225?\013\370t\3728\005\260?&[\237O\227\275\240\277}\006\314\234\331\375\234?\306\365\006\250\007*\265\277X6g\221\021=\223?\277\r\342\344\334\307\245?\0006\332\251\341\246\314?[\326r~\277~e?\371.\247H1\263\235\277c}Q\177Bj\215\277\tq\352\361B\355|?\216\036\334\265\3142\200\277\315J\370\001\257\370\246\277\277f\272\030\306\232\274?eb\30428\004\266\277\003\264\204\245\365W\300\277\033m\326\373\t\264\263?\244\251\n\264\351\002\320?\315\000\313\364\366[\275?\017P\006\231-\022\306\277z5\025Ck\215\256?s\2160\002\303\237\272\277\242<\'\217Xx\261\277\025\207\273\372\027\324\303?=\272\226\\\256\322\270\277-\263\311\262T~\253\277\254\\0\356\201\r\250\277\302\336\223\240\002X\321?Y4\314\352\305\211\306?a\231\310\350\307jG\277\2575\200\"\324\005\231\277x\245O\033\016.\303\277\333\2445\"\t\351\305?\370\202\331\204<\274\274\277\262_F\261?\033\270\277\222\273\274\241q\030\241\277\230C,z\315\322\227\277a>>\213\234\\\306?\364\'\314.\272?\240?\247\rD\375\356\010\224\277\213<\217\023`T\266?\220a\247\367\352+\300?\230\343\373\177y\033\304?\264\253\205\207\347\220\254\277\204<\331\255\271Q\252?.H\222\251n\304\206?\311\237q\225\204\207`\277\323\004N\357\035\026\244\277\023O\246\364G\226\254\277\332 \373M\204\333\262\277\350u\024\2500\361u\277\345\316<\247o\r\264\277\267\255\237\n2E\261\277\272\020\220\227S$\271\277\350h\300\362\260Y\263?\234[\265Z\242\206\320\277\323\346NH(\024\276?;\265\3409\263\330\245\277v\311\021\367\216u\200?-\307!\237\311E\265?\261\332\22437\230\241\277VLD\235\215\227\264\277x\305\360EJ\272\260\277\257\303\264\361\357\253\315?\n*\377\233P4\266\2774\022\251\'tz\232?\302\025\022B\210V\312?\325I|C\266&\307?\224\372B\227\203\364\205\277r\245\260\352\271\330\244\277\020\305\030+D\254\253\277\206\226i\377X\r\263\277o\227\010v\213\006\226?\024!b\217\260\020\241\277>\371\032\345\361a\203?\350b\021\t\360t\252\277\200i\246\272i^\257?\353\245\271\350o\210\315?[C\232\004\211\243\205\277\223\343\360%\262\006\233\277O\247\032q1-\240?\246\341\345\020C\020\266\277X\217\230\2777ba\277\244\201\272A\014.\275?\366\275\341b\370=\261?W\260\272\267\002\353\235?\023\205>\310\006\374\246?\336\033x\031\332\366\257?\353/\251[?\033\272\277q\224\034\003\314\347\276\277\210\366\211\026\316C\256\2774\234wV\315I\304?\217~\2430\027\034\243\277\355\276\266K\021(\301?\205\216>&g\233\252\277\021\270\323%\002*\225\277\341F\212\031\226\226\256?<N\371\"\326\342\266?\236\300\201\257I\266\302\277\300IRVx\312\212\2771\373-\2261\022\242?d\342\005\377\372#\221\277\371\030l\276\330\253\263\277\325\3265:bYw?4l8Yx\024\241?o^\256\031I\355\305?\232.q\356\212\203\260?\245\335\346{\017x\240\277\277\207\376\221\341w\237?\315\234r\035\303\201\256\277,\257T\332\231\354\236?}\036\313\252_\245\277?\324\214\323\301\327\331\214?\004\255\256\340\323#\251?\354\250\203ptM\246?\350\234\332L\364R\301\277\304l(iZ)\267\277\265\277\332\000%\354\264\277\221\016\0279V\245\300\277\013#j\333\354\217B?\320\332G\356\037[\265?a\177;Q\313x\246?D\035\373\362(\322\250?\017\223E\311,\301\262?W\267DpX\014{?\366\332\361\300\213j\254?o\021:Q\365\344\241\277\201\203\037\261\n\307\201\277\231\"\204\'\013\317\212\277\234\234\247\307\016\024\225?)V\004C\220\206d?\364\262\375f\356\273\227\277\022j\025\334=\244\307\277J.\314o\376\221\276\277\254O0e\202\270\263?\024\007w\020M!\224\277\342{\317\000\025\276\246?p-\321~X \200?G\257C\246t?\223?\310\240\265\274\276\356\302?\364\201\312pb\025\240\277\273\263\372\t_,\241\277\317\254k\363\tH\305?\325\311\376\001\033\323\302?;\310B\324s}\301??G<_|\322\245\277\0267\346\017c\370\260?\300\216\346\350\272?\313\277wP\014\260\356\274\260\277m,<\026q\255\253\277\305{\355 \300\025\265\277\325U\275\026\215\374\271\2772\321\226\020\245\021\270\277c\276Px6\375\265\277\315MM\'}\325\240\277\373<3\345\324\356\251\277\300\216\342\025\262n\247\277\033q\346,\311\357\227?\214n\204\277Zz\207?\231@\221>\277\322\245?7\377Y\212\263\374\267?\265s(T\331\274\266\277\343\320\3011u\374\302\277\013y\"\216\025\374[\277\322\352\270v\327Z\213\277l\320\277\202S\276\260?\023\363\211y[\251r?\311Z,\353\253\022\217?\006x\347i\321\317\302\277\376\220\037h\333\224\272\277\370\344\263\2527t\231?\354m0vB\266\300\277\327v*\035%z\247\277\212\332\311p\205x~\277\302\227\021w\201\205\247\2771\006\020\350\025\255\267?l\002\220\237e\375\243?g\272\267g\334\233\272?*c\373\307/A\267\277\340\316\270\272\013v\266?\255\333\013\317Ufo?-k\353\370\373\373\243\277\020\024T2\"\342\302\277\223\007\370z\024\265\245\277o\321\221\271\2705\267\277/\001\330FE\316\251\277\0269\005\202y\205\306?v\353\225\354[\255\265?wC\356!\366K\305?\323\225j\022u\365\241\277\325\231_d\364J\266\277P\005m\"\240\024\272?\257P\367t\310\001\261?\371a\326\351\353y\314\277\270Ih\271\277G\231\277\027{\201\342\353\017\273\277\'\336on\230^\267\277\272/>:<\203\227\277\001\336UW\303\310\200?\274?\346\2643%\300?\022\236\010\373*g\262\277r\360\202\n\202\343\212\277:z\374D\274\322\307?\213\256r\264\323\272\273?/|\014\207\244U\221?\004\263Hw\366\252\301?o\322-\377d\246\304\277W7\264\312\374N\251\277\177\272w\331\300`\260\277R\354\021<\263+\261?\257\203\354\361\220Q\242?\013\234E=\322\376\204?\2464\327\224\342\340\260\277\215\334Z\253;6\314\277@\244\231C\021?\246?\336.\004h\244\340\207\277\350\020Z\2468\311\247\277\225\177EI\020\307\320\277\353L\263\347\213\214\226?\307va\300\305t\256?\007\322\277\017\327\260\265?\026\263\324Evd\243?L9k\276\374Rx\277\305\021\330\035\303w\274\277\327\270\225K\253\n\262\277\022\260\330(\371E\235?=\032:\303;\355\273\277n\312h\005\'l\251\277@B\217\027tX\232?\226\341\333b{\337\256?\373\324\215\343\234\003\240\277\216\250w\236\325\250\276\277l\001&n2\267\261?o`\373\251\371\264\227\277\254\320x\014ee\260?\201\306\203\333\362\006\215\277\314`\362}\226\270\273\277U\220\353s\202\023\222?\302\335\324ET=\312?]z\354\361o\021\240?\022\332p]H\003\270\277\222\360\211\246Z>\277\277G.;>P\275\274?\000\017\231!\272x\312\277\326\014a\362\257l\277?s#yK:d\242?\215\177\370\2171\233\275?\316\002\236\006b9\300?\314\230\276\225\242\240\306?U\371GyS)\300?\031a\330;\244&\261\277lg\275\367\353p\267?\343\023\016kE\025\310?\007{\252\261?C\264?^\227\256\326\023\032\310\277\347\300\342\251\244!\261\277\310\\\256_\256\260\230?\001;\310\207 m\205\277\037d\022d\345\212\252\277\223\315\202&a\333\326\277\251~pM\322\366\252?3vT\236v\252\266?!\335\316\233\315-\267\277\334eF\035\013H\223?\226|\017\013(\215\232?<\257\214\224\256\025\302\277\2233\260\030\217\t\273\277\021ff\223LY\215?\027\367n\270O\241\277\277P\315\2357\226!\325?`/\000AK\303\246\277\2040\224\2716Gw\277\245\256\314\001\236\224\241?\246\354=\207\033\006\261?s{\322;z\035\243\277\006\221\225C\033\377\267\277\034\313\367?\267\312\320\277\213Py\027\221\200\213?\307\204\006\235\221Q\252\277\251\352\342\335\306\206\261\277\353\003\033N\372;\306\277\304\355\022c\266C\221?N+\013-S:\261?e\3553\205\323\025\234\277-\273V\302P\024\231?L\202\274HB\211\264?\350\316\033\243\204\375\252\277\364\225\247\206\201>\267?\350#d\342\340B\247?\373\025\036f_el\277\354Nz\367\252\235\262\277\t\235{.\267\261\221\277\232\231\025i\033\022\231\277\0250\225\320\276\375\267?G\234\357\325\232\r\262?\366\005t\005+\004/?@\223\375t[\025\265?;}\222{\240\353\320\277\"\352\343\341a{\267\2778F\317\364\250\223\272?\025|\350\207\247\223\223?\373\3553\354\270\324\222\277\217\211_\254^\007\277\277K\365\032}\217y\266?y\351\242%\311\006\263?u\003\356n\250\363\270\277\177sux\245i\245?B\321\246\035.\243\254?\\\346\335\tH2\276\2774\023\034ba\305\217\277\272Lj\350\235\214:\277*\034\276\331\350\345\302?\230\02466hX\240\277D\274\202tu\222\302?\201\251\262ft\302\252?\271I\222\274\362\262\262\277\235\025~.)I\272\2771\001g\374\342_\272\277E\2219\235\367\346\275\277\246\313z\261\345\217\321\277\370\357\367B\262\313\242?7a\266l\203\034\276\277\231\315\327&J\001\223\277?\002%|\270Y\253?\336\240\273*W6\222?}\255y\267\367.\302\276\347`\217K\025\365\273?Zf\017\017\036vj\277Pm\267\270\003V\264\277X\035P0Y\307\246?\r\315\034\006Dv\213?\214NB=R\214\265?\301\200\300\001\305\351\273\277\247\317u\'\362\307\266?\230b\206\300\301pT\277\013\301\307\373k\r\257\277\'\010\020>\255\263\211?\032z\212\t\220p\301\277i\351\267\372\306\357\266\277\240\r\210MZ9\301?Q\037\226D \032\200\277\333\233\2139)w\300\277\'\266\246x\0030\263?\022uw\240\345\271\244?v\256\201\373\226\204\242\277\317\223T\346\254\021\272\277\\\354\'0\230\035\251\277\250\241S\006\245\r\273?\363\3113g\275\203\220?i\254\373\025Q\203\234?\267\207\030\r\3377\260?;\311\r\323\261^\200\277\204i\354\362\2056\253\277\352\375\357^\227\034\245\277V\237G\n\306\372\270?\325o\263O\212\026\314\2776\213\034\0224\207\253\277|+\263\')\302\321?\315\243\270\004\013\264Q?\0209 \2042P\255?~\374/\017\307\223\272?\030H\035\262\033\344\267?\035vg;8\243W?\261\3664\331\\\034\276?\331\247e\253\3038\276\277F\331\376\314\246@\265?us7-\347@\261\277\225%\001\330\345\200\265\277\201\307\374}\177\010\223?$\t<0\336\205\252\277m\366\314\364R\347\261?H\t\'.\246\347\322?=\232/\224\202\246\265?7c^\r\374\t\213\277\316@e\014F(\223\277\320\321b\246\3345\317\277\335\325\220\325V\030\274?\014\357?\274\003\335\231\277\300+\232 \023\367\275\277\336f{\010\000>)?\356\242&\303\252\226\255?\322\016dP\017\004\270?\247\336\363\231\267\316\220?\0361\326\272\365\023\237\277\017\235\241\267\273\263\232\277\022\270\237(\253\227\277\277_\270\330gYOy?\207\344MY\373`\261?\303\263\nn\n\317\276?\343(;p\372U\314?@P{\370\200+\262?\0168\231\340J\210\264\277\006\002)k<\231\330\277\316\303\220\242h\221\257\2779&v\230\250\216\227?\000(\3326g\255\216\277\360\020\031J\0347\246\277\376\245\373\023\002\240\251\277\225L\253\030b\301\216?4(\371uo\245\306?\303\234\030\230\252\232\247\277\374\347N\221\007\305\251?\340\177\242\335X\356\247?\344nY\336\311<\300?\322.t\325\225\356\271?kt~\355To\214\277\252\3015mC\002\307\277\031%\341\240\215\274\251?~~\277\307=\237\272\277\023L\023\347\023}\312?\232\005\377\214\305\322\265\277\n\224\320\004\023\224\252\277\364\340\250\005\365@\267?\320\251\374\336\234\233v?\031\n\025YCK\211?a\317\001)\004]\300\277\236\327\255\227L\030\320?\026\"\025\204\020k\223\277\254\241\\\300\202U\256\277sj\204\327$_\263?\241\034\243\205o\235\221\277\365q\230\r\376\341\252\277[\265\024y\325\233\260\277b\251\306\361MH\307?1\n`\366\\?\300\277\204\361\227\367\324\271\256\277\242{\230Y\215(\274?\224P(\205\021\256\310?\214\374\016\324s\200\263?na\022xG\241\272\277\231\207\221\271\345V\241\277\244\256!{\347\374\262?)T\372\346\343\305\240?\276$\237\024\267\340\256\277\220>I\365s\225\240?\212}\254\256\214O\261?7@\237c\351\345\250\2777\255iz\252[\314\277\360\261\221\313\245~\204?\276\355w\335\272@\301?8\236\333\342:2\265\277\265w\030\320\340\337\325\277\205\312\366\301x\233\273?\177o\333O\262v\273?\201\305-\337\360\307\310?$sl\353\312h\250?\317\357\374Y2\256\243?D\336\327\344\3604\302\277\272\224\256\030..\233\277\2548vE\310\020\227?\267?\233\360\321Y\214\277 \333\371(\030,\300\277\240\r$\"\034@\242?\027\007_*\226\037\267?\252_zL\306{\243\277f\312\367\002\210\204\241?\020\2219\3753\n\201?\321\267\027$M\262\235?\227\222cxB,\275\277\321#\2626\207\007\241\277\226\337\303\372\210\337\270?\366\307\325/\022\246\260\277\022\365K\334\025\262\220?\210\230\245\360\366\370\251\277\\\"\227\020\026\360\243\277\203%\312\353\310\240\240?\200\342\362\373B\343\177?P\n\231\277\242\201\232?+(\361\271\217\002\265\277\300\264\342\232n \303\277\234\2772\0330\221\246\277r\217`.W\250\266?K\t\2168\352\256\303\277:`Q\311\257\340\261?Dv\322\233\346\014\257?\327\025u\340E\032\301?\352\351!\326\206b\306?\014\3429xH\032\257\277\307/\265\271~\314\261?\222\017x\261\220\365\303\277u\030\006\036Y]\272\277+\320\361I\275\373\277?t\362G\232~\324\227\277,}\024\202>|\273\277\014\340P\272q\257\264\277kC\343?\273\346\211\277\263I\324[\321\264\276?\031\366\323\177\341\214\242\277\242[\312ex\274\226?\212\030\0005`\355\244?\300\220\322\246e\\\304\277E\177\242\305\"\032v\277\232f\335\270Oo\300?\314\277\352\214\"\263\311\277>\313\004\311B!\302\277}\002\376\215@\000\272?\203I\256\306\346\260\253\277\362C\247\372\201p\301?\333=.w\255\000\214\277.\367\':+\375\256\277\3633\316;\320Z\235\277\205\360b\362p/\241?\264\366e2\244,\222?#zq\217\274\033\264\277_\364\371(\241*\237\277\021\271eGly\312\277\t\340\020\\\251\030\312\277\t\242\014\271C\330\237\277E\315\247E@E\315\277{\324\370wfW\262\277\305c\364\'t\236\315\277W\031^\277_F\253\277t\n\022\215\245&\311\277\022\334h\304\247\247\257?\207m\206B:\276\316\277y\327\347\005rT\272\277:\230\023\tq\370\261\277S\370\n\250\262\237p\277\373\207\002\301g\023\311\277yP\246\r\374\307g?k\031\313\036AH\261\277\226W}mV\t\301\277\310\277,R\205\345\262\2777+a\\\326t\274?3@\240\rZ\370\266?\313XJ\203|\235\310\277HR\001 \250\252\245\277O%\213\020b\207\261\277\305t\020\216\340\205\241?\010#\007G& \245?\251RaI/\225f\277\326 \346\324\236k\240?\305l\263q\217\236\247\277:\2006\205K\272\257?.}Y\336y\351~\277\305\020#\261k\036\201\277\235\217\334\260\362q\272?\263\225\217k9\350\274\277\353\010\232i[\200\315?D\254\353\314f(\253\277d-\342\036\334\274\263\277;\246\244_\177\014\256?\263D\202\221k9\210\277\304\206j6\226\264\305?RH\263lgI\276?I\026^\241dw\211?\231U%\220\351\256\275?\334\025\016ls\314\271?\366\205\263\362o\205\222\2779\241\177\'\356\356\300?\026\244\360.\252\251\271?v\036\336\355Q\367\262\277\005L\330\177k\265\267?^\220!\013\366\210\321\277\244\020}r\004R\243\277e\350\304\337\021@\272?\025\324\003{f\033\300?\022\303\335\204\210\244\274?\345\362\357\010\243\333\233?\027\355\301\260\213\363\274?/\315\007\342\t\276\300?\226\023=\274\221\301\246\277/\001B\023\365%w?\222\344\345\026\356\244\301?\235\254x\2223 \231?r\2511\004\035X\311\277\244\245, \275\226\273\277\366#ZU\030\236\310\277\013\205\023\224C\254\210\277{\340w\223\256\252}\277\'u\206kt\310\304\277:\003\241\341c@\302?C)\332\336O\263\225?D\030\254\0167\246\255?\251\244\005\202g\350\310?\"\313z:|\024u\277\332\325\216\3325\340\215?\313\223;\304S\n\266\277\255A\236\320\336a\263\277\024\256Z\307<b\251?Ik\317/>&\257?0/\006\224\255\"\320\277\321|\373dJ\230\275\277\236\340\311<k\000\262\277\314\307S\270\007\272\273\277\250\003\035)\245<\265\277\264\317\226\300\n/\206\277\321\r9c}j\226\277O\177q\262,\201\225\277\033\226\330\023\'\304\243?b\347CK\365\363\262\277,\000\010<\311\214\276?\304\026\352,N\203\203\277N\342i\332J\004\242\277rp\241\"\201\356\301\2779\363G\223?\355\272?D5\252Tf\240d?\331\304\0314C\340\241\277\347\367$Y;\001\232\277\335\251\272O\223,\276\277<\253\316\254\211.\253?\220k\261J\317\333\230\277fD\224e\025\242\300?\361\367k\267F7\261?\223\226l\003a\363\253\277di\246F\370]\255?\177\233\035\302\210\344\230?\215\314\210\261\315~\255?CY\235\244\341@\302\277\216\373\312#\242\254\251\277\212N\310\027\234^\273\277\270\213\216\313\206\327\255?\347\270\031t\306\257\267\277s\311O\227\034k\262\277x\306]\026^`\267\277\246\373\374\2248\345\230\277\274Z\200\010\210j\311\277n8\260\327an\257\277.\3643\345\004\322\234\277T[\t\211\360\367\321?\025\360\342\030\312%\300\277\247L+\006QY\304?`?\030\301i\300\273\277\233\233\276 H+\305\277\257T\267\305X\337\274?\223F\347L\001\005\300\277N\206O&Z\373\275\277a\02146\333\302\246?\3103>\267\262\243\226?L\232\263\331w@\243\277f0\353\365Q\023\211\277\242X\332R\037/\202?\361\313m\212F\206\252\277x]}\257J\336\272?\271\177\016*:\021\221?\355\000\341Wy\027\235\277\343\3442G\215\357\257?\201\351\222\353T\276\307\277\347\223q\t]\272\220?\372\024\216q\276~\272\277l\036!\3604y\314\277\321\330\343+\223\372\264?\374l\370\267]\310\225?\3342 s\327\215\265\277-\356#M\326\334\240\277a\306$\315\t&\213?t\303\376C[\250\300?\241\241\207\364\340\r\321?C\312\265\301\310M\254?f\266t\"^\033\306\277\205Sf_Z\224\213\277\216H\252\321!\344\261?=g\020\307\263\317\240\277\025CK\277\376\013\273?\021\036\307\202\350|\306?\351\207\233\216,\207\245?\250\315\351r!\273\247?YvE\224\342q\271\277\2632\261\264\203\354\245\277\265\241\261H\360\363\204\277D\005f\267\021\350\256\277.O\260\245\345\343\267?X\020\276\340P\014\243\277\353*\330?\325\273\247\277\2201\342\361\273Q\313\277:h;\376\"+\234\277\005\365e\023\246Y\263?\302\336\312\005OV\207\277A\034\3505b\256\234?t\211\200>\363\371b\277\214\263\254\221\322\347\261\277\322Z\205\005k\214\245?\337\031\327\357\250O\210?\342\246\026\3478T~\277\237\232\372\364g\254\225?\304;\177c+J\312?1e\311\267\340d\266?\230\372v\212^a\261?\224\006DT\n\310\275\277\213\353\307e\020L\333\277Z\340\240bR|\304\277\367\3376\005\372\353\200\277\002\025D\327\305\274{\277\022W\026\367\362[\226?\000\334\360\334\315_\237\277A=\373m8\325\261?\027\312Hz8|\276?\032\2520\221\363\323\261\277\230e\367\345J\354\231\277C\006r\234\316K\304?@\333\t\304F\035\226\277g\257;\355\000\216\275\277|\350\230\277\0034\274?\013\023~N\025\302\314\277\006h\'ro\307\267?\252\266Y\364bq\301?\010\233\211\331\351%p?\257\311\207\037\214&\311?YI\363a\r\221\223\2773p\367\202\322\005\274?\202BD\267\204m\266\277\261\005\353\004y\227\272?h\272z\223\276\354\245\277\177b\037\260\n\035\222?\026\323\342r\341\234\246\2770\241\243\3662\264\260?v\253\345\006\240PF?p\037\312\336\232J\270\277f |\302u\301\305?)[\204\336\377\205\203\277\016\340lJE\307\256\277$\311BP\201G\227?w#\230\351\234\314\256\277Z\323@\226\324\276\315\277\010\322\207Br\216\266\277~\342\314\233\020\322\244?S\034\024\231-\203\274\277\021\352\202\325\023M\303\277\350\203gc\230c\266\277\232\261\263}\3229\256?\212\005G\2628\036\265?:l)f\324E\226\27793d\223#\302\241?\370\0102*\375i\252?\256\323w\037\340\242\260?\266\023}\232\263\221\263?\207\204\274\211\205z\\?\222I\315\301\0300\223\277l)\324\230e\225\276?\261fC,\215\212\267?<\'\340\300\370\201\226\277\200p\322+D\207\331?\323D]i\223\t\222\277Y\225\2740\022\013\265\277u\221\017\030n[\272\277\001\267\336\334!\232\303\277\326\313\340\276\351\023\304\277^\302\037\233\036h\217?/\347\352\020\376\025\253?\300=\177\272|C\301\277PH\355w5\314\227\277\315H\241*\245\203\253?\016\302\327\363 0\311?\362=\\z\367t\251\277\336zk\326\335\377\210\277,\270\002\3055\366\304?F\023&\254\200\002\274?Zi%RI\236\315\277\332\337H\234\350[\235\277\374t\250+\343\001\312?\242\307A\034!\036\263?\253\2430\036X$\237?\256>\314\036\014\265\177?@\324cJl\363\300?\342\242\n$\355K\224?\242\266+*\210\034\260?\362\341\025\345C6\251?\022\370PA\301\036\203\277\304\340\204\252\"/\265?c7\277\017\027\330\314\277Y5f%\301\007\223?q\226\007\343\225n\252\277\014\223\026\320c)\265?L\351\331\002n\243\200\277\255\274\234/\001\010\254\277\021]\326\3318\371\257?\351\232\265\253\356\306\326?\251\334\345\224G\363\271?L\254;I\355\203\273?\026~nq>\323\253\277\360|\227v\225\232\246?\262\202}\034\006l\271?\030[y\253!\304\177\277B\253IH\351\276\277\277\023\016z\304\342\272\270?6\326\261d\216V\263?\220)\214@\246\r\274?\204\333\'\241\276\303\341\277\317\211\214\216\304\312\236?j\233\227Eo\250\311?\322\222\013\343\323d\303?\277v\020\336K\242\212?\241n\315\373\264\001\214\277\277h\373;\363\350\227?\265|b\022RTu\277A&-\332x\377\242\277\254\233\230Z\370\274\214?^\231\367]v\261\271?b\"\255\223\215\317\241?N\024G\306]\005\273?\2507\007gFY\265\277k\035f\020\276`\320\277r\034\004\017\"\022\244\277\371\002\341F`\200\302\277u\353\265uS\236\267?\235\2432`%R\316\277\227M\247=uA\236\2777\211\252\307\336*\241?fv\207\001\002\031\300\277\323\270\353u\370M\250?\356\362\304UYf\236\277\310\005\376\027\271\010\250?@\031\320\300x\003\265\277\'}u\202@\306\246?,\226\325\ti-\252\277\370\251\313\353\2017\312?\245\323\033\371-\237\261\277\253\024\350\370\037m\230\277\010?\310&d\311\304\277\005\266f!~\000\263\277\177\256\266\265\337\177\275\277\031o\246t\3541\255?\274\376\201\247\003\204\264?\221\027\214\200e\344\301\277\203\351\374*|}\266\277\024/cq\315\310\327?\243\344\254\365\203x\266?\216i\352\205\242\274\306\277UN\323Rp\361\264?\\\263\007\035\274\236\221\277\214>\326o\253*\266\277\261\232H:\"`\266\277\207\243\261\325m\277\245\277ci\223\314\202\245\271?\r\t\336N\223\211\237?\267NDW\274\247\272\277\374\215c(bv\300?_\315\r\357\251\002\221?V\344g\340\005\215\270\277x!\224i:\233\277?&\303Q\204!\320{\277\261\\2\305^\026\300?|w\325M\355\322\260\277\335\262\016Y\314\373\310\277\332\223f^H\024R\277\314\254\330\021\032\214o?\276.\002\027\031\373\265?\206R3\246\331]\236\277\223\307\033\265\036\247\252\277\031\347:h\215\"\270?\354#\3710\227\250\212\277v\224\335\352\211\376\212\277\3659\277\363<\177\264\277\347\311\274*\346;\236\277W\225?)YU\244?hv\202\226Lu\253\277#\277\227\321\230\363\261?\270-\212$wh\312?\242\026|~B\363\251?^\205\265\254bM\301\277y\276\216\207\257M\272\277Y\205\'(\\a\300\277\250\241[\200\242\201\267\277NY\211\030\343J\307?\317&\265\"\201\250\267\277\266\225\244\253}U\257\277\344\223\323\215\236\274\220?\034\361\336\341\342\355\247?\243\177L\302\302\030\256\277\223\n\335\nlS\235\277\273R\202\300\014\010\251?^\243T\3532=\266\277W\"ci\237\313\300?V\341b\215t\255\260??\253\2367N.\273?D\214jv<\022\236\277.T\326\\,\\P\277\\\323X\215\245<\251\277\034\254!a\245\346\326?\227-\340\317\001\301\202?\363\360b\267\274\255\205?\351\323\032RX<\301?b\031\2701\264\022\264\277u\346\274\352~?\251\277\0071\007y\"\022\300\277\322\0026\375#\340\303?\253uEP\333&\255?\216&\327\306\355\340\303\277<[\345\r\204\025\305\277mu\023\3238\007\243\277\242\347\2135!Y\306\277\027\237i\231\337\376\251\277dG\027\334\275\226\241?\370\225o\255G \202?\2172\245sA\017x?\214\364\336\312\020\241\302?L\370\016h5\033\241\277\306:\305\326;\207\300?\235\2028\005c\303\257\277)\374\3565\203\344\304\277\343\221\210\341\330\252\210?\376\246Yy\370\351\206?qO\271l\216X\226\277%<\231f\006\000\270?x\354\001\210\336\035\203\277\340\007\233\210]\213\266\277\210\035wW\023A\266\277\266-\330Cn-\250?[\271\320\225\374\371\243?I\"\231%\010\022\272\277\350\365\200\026B\006\234?!\245B\247:\307\206?1\025\335\256V\272\311\277\346\250\376\336<\244\307?\336fu\317\002i\271?c;\002\267\037b\231\2779\244\302\355]\001|?vf\023ey{\272\277\371\243`\027`\273\246?\334Pv\342\236\033\205\277\317\212\354\n\356%\270?\265;\363f\274\021\261?vh\350 \201\277\206\277d\372m\237,g\247?v~*^B\026\205\277/\357\275\025Q5\274\277\032O\322\217E\222\240\277\272*\325\320n\311\235?G9\321\324\353\301\230\277}\253w\237\347L\260?!2l\253\324O\301?\005\035\351:\245\377\275?\275\3344\373\326\225\251\277`\337\245\356\025\323\260?Z\241\227G\315I\331\277%\266\311\306\241\036z\277\352\246\321|{\324\213\277\364\007\311\355\2313\244?C\341e\376\025\324\212?=l\220\251\034b\240\277\262Q+n\335\t\300?\342!\244\347\001B\316?3_\307\353} \256?nn#dR2\261\277\334\215\n\333\225ty?6s\275\257\323|\203\277b\224\361\246.\374\244\277Zh\205\213}\234\211?)\201\265\025\376\214\264?\341\366\274\337\357\235j\277>\026\361Z\034\242\214\277\035\013\002\0374\352\264\277\036\023\036w\004^A?m\312_\205\033{\260?i\343\300J:\274z?\346~w\356\274\031\270\277\362\230\347\0360\211\253?\210\271;/\344!\241\277\tL\250\3157;\313\2772\266\360\262\334\336\262?1\341_8\211\n\230\277\271;\020\276\346Q\267?13\\\243\377-i\277\315\002g+\343\357\273?\032\033\256-\350\277x\277m7W\237C\226\251?R\373s\257\033\251\254\277&InH\265\331\267?\217\320\203\t\377\302\241\277/\356P\352\276\326\234?\377nj\330c\013\256\277r\374\032\360\3531\266?\004W\274\245K\210x?\0377\337\220\311 \265?Y c\010F\201\211?\231\302>c]G\311?##\361;\256:\224\277\357\315\355L\366\334\274\277L\305\322X\336j\262?f\322\331\375U!\257?\242\\E\264=\021y\2772;\332\202\324\246g?\245\003\017\037\342\342\245\277\372\234\341\242\263\337\302\277\304\307\302\367\217\020\261\277g\360\'\336:\355\226?\377o\332Ab1\233\277\233\224\210\325<\336\224??\303\240\231{&\222\277\362\246\3063\373\350~\277\0255\267\377B<\307?\216\026>*\307+\205\277\254\037\036\340\372-\271\277\316\266\372\023\317\312\222\277\377\027\233\351z\007\204\277u\332E\237t\233\247\277\"P2P)\005\240\277\024\372>~\252\205\264\277\375\361\032\225\237\316\222\277WH\311V\371\026\231?Ourh\355\234\275?\326\351\036L\036\227\270\277v5*<\207%\201\277xE\326[\274\253y\277\002Q!\264r\361\251\277t\221\362\320\312\253n?\202\246\240ae\205\227\277\375\030!$\326\276\234\277\330\240\254\237\200\020n\277\327&\027\2115\177\240\277\354\230\316\376\321\014\264?\017A\036u\235\t\303?\351\347\366b)\021\246?1\\\350S\315\007\263\277\204\355L\236\275\014\256?\026\373\200\311\3733\304\277A|\212\233\266_\265\277\217\331\030\013U\234\252?\340S\t\267P\324\301?\206k\273\372d\213\300?\377\350f\334\016\030\271?%b\021\227\004\203\245\2775?\"\nx\t\240?\024\353\244\013\'!\230?\020\3228\205\257\024\225\277D\363\354\324\345\221\324?\311\2306\364s\023t?\306\326|\252\006\247\300\277&%4\200h@\253\277&\036\002\272\032L\274\277Ux\217\216\244\375\265?\233\361\203\355\032\n\255?\177\225\327\n\3576\205?\024\216x.\247Y\263?;\234\220\236E\214\264\2771\270\340\245_\352\303?\367\275\253\206x\035\274?J\357\027\t,\003\244?\231\306\2568\234)\304\2776P7q\226u\273?\267J\266)\261\335\240\277\214\020^\303\275f\312\277\203L\237a\231A\224\277e-s;\276\212\213\277.a\264\026\357D\262\277\302\243\265\307\270\213\301\277\352w\306\027l\234\317?\322M\n\022\3324\277?\241\006%H\241R\241\277ZT_Xa\301\311?d\017\177,|\302\235?\353\310\307&\317X\307\277Rvm\033w\237\260\277\353\300_\230\023\300\341\277\230j;wVC\262?\315\216\352\226\250\036\240\277:\302]\332\372\250\272?\346\251\375\226\261\231\310?\t\201[6\375~\252?\211\303?\272^\013\227\277\347K\022_\230\020\304?G\023/\336\037\306\210?\214\006\3656e\030\261?\226\220\017\210Bk\301?\334\033\372\220Z\233\223?\365-d*\373\036\217?\322D\210Ms\234\211\277o\026@\362\'\\\270\2779\311E\341\230\265\204?\377\260\241\351[a\263?w\374\253\301\025\256\250?|\300\346vO\266\260\277w\251Q\307y\352{?,n\307\215\300>\242?\200\317\236\343jM\227?tjQ\222\217b\251?\250]q\364v\321\263?%\234h\217\357\234\202\277\225\002]rL\275\267?\2713D%\310)\265\277u\2029\002;B\246?i\325\216\340D\371\312\277(\340\255C\325`X?\267h\246\376\033\332\267?C\262HV#\210\267?aSzh:\200\272?\322\233W\025\332\'\211?FX\233\324\004\031\252?\013.b\244#\\\232?\357\235u2\373\266\261\277E\206\225\270\312\025f\277\371\360\321\214S\213}\277\331\200\036\362\371\345\303?\343\225Y-E\260\311\277\324\3728@#\t\222\277\2249\241\2234+\332?\0041\204Y\272!\233?\314T\347\341\275\357\302\277w\326\327/\023q\244\277!\311m\322d\021\242\277\3145Ws\354/\265?\032\341\026l.@\304\277\335\262\221\317\326\301\321?\373\263Y\223\341\005\204\277\363lM\222=\252\261?\247QT\306\007L\255\277\377\250\265\354\320d\213?\211\215c\375\256&\263\277~\001&f\n\211\301?\247<\346R?\350\314?\355\205S\275\220V\243?\333O3D\245\207\265\277\234\316\350\320n\302\331\277F\t\234\274\351\005\254?\244\371+\225_\327\233\277lv\361\372\211\240U?\220X\311.Ga\273\277Nj\304Px\021\220?Q\356c\324\270\347\265\277\205+\'\2042\257\300\2774\371.\351\276^\260\277z\367\234G\346\366\252?\217\326<\272\007B\254\277>o=Mf_\247?>\370\306\246\"\365\210\277\223=\"\204\242_\300??F\312\236\202N\214\277ml\003\350\202;K\277I\313\231\314\354\277\221\277\3415:\323x\231\312\277\020\341\313\020^I\241\277\tq#y\034\223\242?\t\327L\234\275\217\273\277\030\304\243\036\3572\223\277q\307\304\233\236Z\255?X-\253\nm\r\313?\014\0210j\030\371\272\277\303\017\346m\326\005\265?K\206\252M\270\245\242?\227\275)\326\203\333\237\277\327UAL\270(\264?\215\211\034\225\364n\313\277-\r\344O ?\246\277\n\024\004\340\355\276\311?\270\267\326\300[\327\272\277\301\342M\273\325/\232?\343A\237\201\3749\226?\374\225\031:\027\253\241\277\306G\312\377)\235l\277\001\306\027\340\013\177\242?\355\027\324\2760\036\250\277G\377\356\244\336\364o\277\036\213\231V\341n\246\277nrz\301<\004\273\277\355\233\342kyZ\272\277\204\312\034e{?\242?y\312\311h\177P\230\277^\322\343\255S\374\262\277\365\235\227\316\364\344\311\2779\244\246\242\355\020\215\277m;\271\256a$\277?\224L\265\333j\352\303\277\262\366%\030\326d\275?@CQ={\302\224?\006\r\257\306Z\364\274?\347\373\227K:\312\313?u\335\203\221\327U\252\277\313\356\203oBD\254?z#\236\201\305\270\276?3=8\217\357\305\240?]\352\1770\2768\212?\014\350tp\010\373\200?\000!k\313\240\242\254??h\215~\221d\272\277y\005\031\323\300\363\262\277\006\325\003\230J\325\262?}V/Ad\207\262?\233|i\311\213\244\270?_H)\254\255\037\302\277\366\313\254z\236)\267\277\207;E\\\036\213\266?A\350\034\224\022k\224\277T\253\2249\334\264\260?\352\236\301V\242\341\311\277\326$\373\204a\007\203\277|\237\036o\322\207\317? P\223C\374\333\300?v\357?\240\225.w\277\232\nw\350\222U\270?\326>s!\212\016\321\277o\272/-\t\355\223?w\303\336\205\325o\305?\254\212!\370\252`\312?\035\333\007\356\001*\322\277\027\272\373\023\334\021\254\277c!\204Zt5\262?6F\205\231\207\310|?\373\270\'\240\207$z\277>9\275\2000\031\225?r`]\273\336{m?L\352oJ`\314\234\277_\366mak\261\253?N:2\351\322v\303\277\257]\306C\232\027\323\277\017h_\020\310\334x\2779P\036\216\354\234\265?6G\034\r\020U\300\277\315\367$\216.\223\302?\366\361\004\216+\261\241?\216$\021\356\2212\262?\251U]U~\200\257\277.\222a\302\365\001\240?g\332\035\232\357\031\207?\020\262\270\220 -\247\277\235\205*\212\250a\264?\005(-\311\366\257\222?\324\213@\313\003\334\265?\347\235p J*\262\277Y\025\304:7\274\214\277\302\324\334\233E\033\234?\215\007\273\370G\213\265?\337\373\301\312\013\017\254\277/\261\027\222w,\261?q\372\300\214b\200\264\2778\305G\307\276!v?\264\367\261\356-\027\266\277\263y\204/\263\021\253\277N \334\264\356H\262?\003<Q2\3565\227?\237\241;w\342[\256\277\340\313\366\003\356\233\237\277X\336g\010Vw\310?\257\375\207\370\264\014\243?\324\237\274\315\323s\241\277\216\275\301%\311\264\215?<\336\363\021P^\273?\323\013\371\355\0062\221\277\212=\323-3\204\307\277\001\250\256\304GZ\275\277\010KY\332\245\341\236\277N\"\016\3479\362n?|\223\216J\250\215\212\277\227\035\243Y`]\250\277\303\2313R\307\272\270\277\242q\265\273\302@\303?\2660\320\205\304\023\267\277\373\267%\007d\020\207?\224\373\304\244:B\266\277\365\'\203f\213\230\311\277\345\006E\376\201\361\305?\225m\004\372\037\376\225?Z\301]8J\362\265?\'\004\341\305\2711\241?B\0258\23561\302?\2647\207\t\347#\263?\355\r\271\320$\216\231?\375\220\242\274\tr\275?\265\255\212\301\314\220\261?m\010I#\246\223\303?\370\274\207g}\332}\277\371?\3148\2116\302?\025\3078Na\215\222?\225\346\303\010\256W\305\277\263\313\010\034\310,\251\277n\362\014h\027(\211\277\207;k\003\353\002\276?_d\242{\373\t\256?J\377\376\207\237\332\231?.\305\204\365-@\244\277\254\317\303\227\003\333\233\277\021v\372\252\022?\263?\\|\241\361\245\221\263\277\267\352o\375\265x\214?\365|k\266n4\321\277\034:\244\014\366\327\255?\317\353/\307\222\367\273?\225\306d\347\304b\273?)\215\0067:\254\257?$\235\220\037\023\024\246\277tB@=~\236\303?\020F\250\236\226\237\302\277-\362\362\010\374\257\221?\345\371\207\033\372\257[\277\371\264\232\356\246J\265\277c\374\373\332o[\251?\2750\312\324\313V\262?\300\236B\352<\227\252\277o\263\2743\274k\247\277V:S\345\211(\236?\244\r\255\243\332\315\252\277w \346\205\261\263\213\277\211\\\006\233?\205\272?\007x\360z\224\221\252?\027\241D(\374q\263?[\365\'\036]\202\251?z\003\034\030\363\232\306\277\373\271\007\345\350C\244\277\0033\377l:\220\265?!Z\217\277pz\300\277\371D\001M\033\301h\277\262+g\203\2340\212\277,\323\300\350\303\363\303?\253Q\231\354\226C\274?n\215\264\203\307\005\265\277#\243\266\324\214\203\300?\220-\026-\332V\271\277\371\305*\272c\356\270\277\336\270\001\237\220\256\300?\311\240\323|\220\005\305?\005\273\205eWo\270?\266\227\255\333\317^\207?(\362\005\311\370\240\233\277\355z\373\372\252\375\310?X\320n\010 \276\244?I\265q\014\372\000\251\277c\266\264\230\320{\271\277P\346}i\355\244\260?\306M\301Y\366W\256\277\324*\327\035\373\365\251\277\266\264\252\373\325\033\200?\335\361\313\334H\364\227?\221gx\007\331R\302?\rK\320\242\264\233\204\277Q>\002\025\302\002\232\277g\254qyl\016\243?:\253I\256\350\216\200?|\353\233\003\273\026\300?\241)\204\004\200\221\270?\255\002;?\316 \213\2779\247$?r$\263\277\000\034f\204\312L\264?0\354\307\226y\256\271\277\020\220\207`\333\215\302?\326\227\205\tI\343\316?\240k\034V\334\330\240\277*\264\250\347\224\276\266?\331 \027\277\000\257\254\277\005\215\277\256\005I\260\277p\356\363$\373\277\225\277Fd\310\227\201\031\302?\242V\277v$\243\301?o-\357l\273\007P\277\r\024\367b`\363 ?w\001\312G7\340\300\277\343\353\317N\016\363\277?\242w\373\373\320_\210\277\035\023\231o~\335\270\277\347\372\3106\r\"\240\277\\\366\330\0256T\262\277\257\021\334\010\326\017\304?]\267e\013\\~\261?\342%5\274\233\342\300\277\024l\376\220\020|\244?\n\256\035s\017\325\305?\243\255I\342\260\032\271\277\027\351<|`\312\262?\\\371\024\301q>\230?b\327\246\210}\240\245?o\2455\306)M\210?\261\315\276\224\266\324\251\277\202..\275\254\315\267?_\365z\003!E\242\277\036\024\001\2721\246x\277j$\342\034\"r\231\277\361Y\221\027#\357\311\277\334Ps\240\363-\216\277\320\201\321\031\335\230\210?\r\207\343\241P\227\255\277\004\004\326\302\264\036\322?\004\202\300\246\203:\255\277\224\021U\203\207\335\273\277\335\204J\240\255\032\233\277W\233\007\242D\313\243?\273I\305\210\261S\277\277W\0213>\231\340\277?\367=\343dz\256\315?QGp\320\245\006\254\277\301\211/\013\254u\276?\037#\244\224\014\217\276\277W-\352\247:E\270?L\370\t\330I.\264?\t2\217\271I\265\312\277I\304\207\240\364\221\252\277\002@p\306\212\320\243?K\317\362(\275\370\263?n+\016\360\267E\325\277\222\031\335\023?9\256?\t\241]\007*\004\233?\032\212k\242c\250\261?\010;Q(%\334\303?\026u\235\177\203\224\247\277c\223\224\237\0267a?\341\336\361\376\261\204\253?g\347\276{\r\241\271?\215\326\266\220\306\032\272\277\230\244\010\373\236\250\262?\343\322\004\330\036y\224\277\365\307\233~\263\337\260?\375\265\"@ta\225?\233\022\312\005\271\370\256\277\rG\225\2109\266\211?\312y\006\351w k\277\225\t E\204\024\303?B\343\222M\312b\266\277\n\332\247e\264\230\253?\314\262\365\206R|\270?\351\200\271\026=\025\273\277\261Z\346\313\272\237\255?\004\302\035\333\362\343\250?\np\215N\277\271\301\277B\327\205\030\372\270\242\277miR\320\362\r\233?\372\327u\"T\215\264\277\023UX/P!\312\277Ct\366\330\312\323\221\277\327,\230\267.\277\237?&\221\212\347.\366\243?\337\'\2077\037\361\311?\237\264\002D\003\303\222\277<\323\005z\037\376\306?+\000\000_\346\\\301?/\227;\253\221\351\246\277\023\372\305Ms\240\214\277\250\371\'z\243\242\301?\013\244FZV+\302?\261\346\"\014\004\256\276\277[\323\204$\344\177\245\277\213\035JIig\236\277\320L(J:\211\202\277\3557M\256\214\340\244?\273\370D\255[\201\250? \001`\037\205\211\234\277\337\373\315aTG\254\277\023\364^(at|?3\264\331\026\342\233s\277-z4\372\t\242\304\277d\215\335\262xLw?\245\213A0/\225\205\277\212\\L\034j\361\262\277\223!\224#j\341\260?rD^\361-\002\221?P%\022\331t|\301?\'\243\023\033\017\341\271\277og?\203\245\307\246?\272\223y\322\277?\265?q\267\303\321\004\275\276\277d\006\030\324\373\367\261?i\302\200\246\250\303\271\277\311x\034l\241\347\266?}4\226\332\377\256\231\2773X\263}\321\200\245\277\265\233\303%8Y\264\277\017\026\204\351\2246\262\277\037\327}\r*\376r\277\224!\375\350\243\021\301\277\177I\361\207\271\314\313?\016\266I\304\366\327\253\277\346\205[e\336\250\226\277\003\236\222\367\214]\202?\242\213N\221\362\222\306?\256\244e\344 \035\247?\320\006gS\353\230\304?\217\317\242\014ID\260?\275\222\214.\021u\263\277\324\225\177\314~n\250?v\353\356\241RE\254\277q\252\244\346o\273\327?\207}^s\235W\223\277\250\304R\353\343\362\237?\213\354\n\257\235\251\316??\256\355\316\316P\234?l\323z\026wO\244\277p>\262#bm\264\277\327\344\230\372G}\320\277\177q\206\017\303\362\270\277\235\366\010\357\2310\272?S\215.d\036\327\243?\001S\274\330`\377\312?:\202\022\217`l\252\277\022\262\341\024\203g\244\277\343\021\211\246\350\251\336?l\230\232\004\342\217\221\277.\321\301-\003\r\205\2770F\261\236\246z\325\277\210\243\365cW\365\267?*\306I\246\2359\215?m\033\245\330\366\363\277?\031v\335z\271\323\313\277\312{\241\033\232\010\245?,\224\317N \270\265?o\277a\263Lu\231\277\r\004\333\257\210+\273\277;\263tHp\024\273?([\211u?\362\243?)\362\341\335\317\273\223?+\005\032=\345\377\221\277\nc\243\305\316\326\244?\033\000\303\371\220r\231\277!\250\203*\023\307\240\277\245\246\307_\342\204y\277,Q\225+\321\263\251?Y\371|\353Mq\320?\355\021\035\256\216\0306?=\n\202\267U\000\230?\325Iy>4\224\267\277|\357\320s\253r\220?\017d\322\320\033<\257\277\311h\277}\260t\253\277\256G:o\"[\257?\320 q\316pu\245\277\340\240\272h\325\000\242\277\342\325\230\363\200\035\325?\230\236\301\035)\260\235\277I\200\306NB3k?]\230\210Aqt\276\277\220|\367\334lU\241?.y\222\2729\272\266\277\003\354Zft\334\207?,\301\217W\024\211\270\277!\323H\rSH\246?\026\024\305R\342\233]?\352\374\342\274S\263\202?\203\265\343Q\001\267\276\277\205\341nG\323c\270\277\222\365\237Y[%\265\277\007\243[M\\\003\260\277p_&t\377\211\302\277(.\322\302\363\365\300?gr\r\336\355\246\255\277\255\3748\330\010\337\306?\007\310vHD\273\304?\010\244?\t\345\t\203?#\205\3018\250\177\261?\212\310n\t\037C\302\277<\201\365k\t\376;\277\024\037\336\303\023/\304\277\277Y\307\2046\r\304?!x1\230\221N\202?\356|\324\352T(\266\277\3644\32050B\273?]\"\257G~\343\274\277\351\377\323\364!\r\220\277g\333\032\270;o\261?-\301\3210p~\266?\322\374\022v\331E\251\277\270\020\370SP\266\266\277)\311\005\341}r\263?L\006}\205\035\260\277\277\304\356v\213\347\227\255\277v\302`\356r\"\260?v\2209\177\017e\271\277\232%\004_\244\256\264\277\245\337cS\031\006\215?\266\267U\215\207\024\273?\300\022\010\312\322w\301?\025\330<\255*\337\277\277\321\3547\232\262\232\275?\341\033\221\344=.\260\277\255\207\304\3410\261\244\277\317\310\233\251\245D\261\277O\037\352\021\337\221\242?\237\226\206}\352&\263?\366\237A\300#\342\273?\376\250\347,N\306\211\277\252Y\320\375lV\251?T5\t/\375\r\260\277\225\'c\354\210\322\262\277\354\2417\261}\315\245\277\001\250JKq\275\241\277a\227+\005\014\023\277\277\204\r\303U&8\262?y5\334J\316\314\303?\235\235c\214\206\304\217?\025\337Q\206\235y\275?R\274\253\2604\037\277?b\027\026\325\352\242\311\277j#\211\014\252\363\306?>\343\244\234\277?\\?\03421\203/\312\270\277\251^;v\257\361\277\277\352\002\325\370\240\350\247?S`%z\034\236\270?\371J\243\375\002\236\262\277\207\233\033nh\275\274\277)\351\262#\235\254\261?b\311\254`\000\374\256\277c\335\244\033\254\343\234?\211\374\223\325\303\n\246\277\342=\3738\253G\241\277wV \265\023\337\263?\027\215(\236>\313\244?;\021\316\022bP\302\277-\253\374\206>\371\260?\034\321^\226Rd\254\277\230\313e\013iS\275?\231D\025\\\337\204\322?\214\255v\022\177\031\202\277Sp=\321\261\231\227?\352b\337\353\216\n\250\277H{|\000\n\271\240?\036\356\233P\002\320\246\277\205Z\371\362\"\n\241\277\026\271U9\360\357\270?e\270\267L\210\323\302\277g\320J\231o\220\246\277\237\\MG?\365\260\277[\256>\013\355\257\247?\364\016n\361W$\271?J\303\336\3424\343\245\277\021\373\302#vS\302\277zM\262\271\371\323\225\277\376F8\204\332\\\261?\312\036\311\345;\221\262\277\377\275!]\337\205\273?\350\215\303\354Q\221\266?\334\351\240;a\317\241\277\037\274[\333\031\177\270?\023\367F\321\221\365\261\277\036s\245\346\362\377\241\277\200\347\337\3725\031\251\277[6\376\217\241\306\301\277X|6\330\366o\213\277\024K\275\263\001\226\300\2771\000\306\334\024\214\242?r\231~a\314I\265?\007<)>\214(\275\277\016&\217\355\205\223\243?C\374~H\224\260\236?\320$\027\370\354\027\240\277\211\237\311\322Y\366\214?\372;\332\037\373O\263?\277\324\315l\205R\205?I?\260\317C\025\224\277\304\342\245m4\256S?$\276\201\'\347\373\307\2774\002E\313\237\233\222?\'\320\317\022~b\307\2779O\313cd\334\324?h\010\326\3507\315\260\277zo1z\256\001\214?\300fy\007Q\265\265?\363v\037\205\240\\\324\277\331\250\260\230\344\337\220?\316\000^\301\310\017\234?2A\256\376P\261\260\277\363e\2678Vc\251?\201\274\241\020\206\316\304\277Z\013\227\302\225\226\266\277\350=\000+\207\025\321?/\270>\003\210\251\254\277\3121\247\252\364\346c\277\251\371\023^^\341\300\277\213\345c\006\010i\235?\032\322\353\240\230\004\306\277{\213\274\323j\275\300\277Oa\037\244:\306\250?\253\205Y\335\027\273\301?\320G\241\273\033\253\270?\030K\333W\333\344\235\277\037v\244r\266\036\253\277\340L\315\030\241\247\267?\3265k\270\371\021\230\277\364\016\016\037\230\221\303?\323u\267\250\272h\300?\307\235\375\372\037\236\263?\325\346A\271\240\212\315\277o\020\271\342r\035\265?/\216\276\0303b\262\277V; R\213\351\315?\3723\352\347\257\035\320\277\357((\306\213\036\257?\337\303@l\366\340\307?d\260y\tz\265>?n\275r\\\216y\236\277\'\332\273\245tm\242?N\342\032\305F\215\267\277\277\014\342\327\361e\273?U2\337\017=\320\256\277\201\332jT\243`\315\277j\243\225\201\372I\315\277\246a\264r\026\001\211\277\351\361\366^r\364\306?;\336\003\334p\241\247\277%\244\214\246Q\337\314\277\317\275\261\311^\373m?\356\246\360O4P\226\277h\006V\260^\357\247\277\373\200\307\001\317\306\253\277)2\367\221.C\264\277\335ci\340\255\036z\277\016&\202\026@M\300\277%G\3716\016\275\251\277\250\237\247\\\223\250\267??\365D\357\335\200\304?L\254\337s\177\315\300\277\327\006\217!\200\312\264\277\341\024\210\253\007=\221\277\276B\245M\'e\306?\246\036\302\357\016\211\266?^H\263p\240?\267?\344#\302q\207\224\270?R8>6\221\215\301\277QP\261\021O\261\263\277\006<\354y(*\252\277\377W\361\353?\355\325\277f\022\315Y\213,\266?\033;\036\326\026\310\224?\361\326\t+\311\343\325?\257\334\2076N\206\256?GX\242v\351\336\247?\375\000\206g\301|\241?\221\315\240\254 \355\302\277\350_~\364\361|\244?\325\307\340\022O$\260?\352\254+\240\206\365\236?+\333\355\261\370\332\240\2772\331u\244sN\276\277\376\301o\364\316\250\274\277\t\332\272\237\201\020\250\277\204^\302n\305\007\231\277\214\352\341\005\014h\261?&\261M\304\252T\301?\005( \324\361\226\276\277@\224\253\251~\037\317?b0@W\330Y\316\277b8\022\006<\261\227?\214E4\n).\231\277\205D_m\352\273\271\2770\253k\333\243\r\250?Xh\372A\202a\243?j$\204z\271\010\244\277i+\030\340\302\314\277?\301\263V\024mT\303\277y\354\376\205\344\276\236?U\242\277-fO\247?\367V0\253\301\255\244?Xr\353}5\026\256?C\360lh\264q\261?H\003\267\034GF\321?F\260p\'\2529\320\277j\246M\304A\022\262?\254g[I$2\302\277\317\331\210\322\257\025\306?\026X\356\300Q\273\317\277\340P\217\272\3126\252?\205\235\213i\342W\262\277\345\230rw\322=\263?\320i\301W\240\343`\277V\366S\334{\024\255?1qQW\262\345\260\277\2229\232v\260\202\272?\366\375\020\217a\372\227?\"\252\366\326R\025\256\277\213\355\366\\\020t\305\277\232\355\244so\365\250\277vM\346W\030R\254\277\336\007\016 \177\355\277?\277T~(r\016\220?\233\206\320\315j\222\272?\244\020\267^\314\365\310?m\313dU\3749\321\277\261\232\2154d4\224\2776)\200\006\230~\270\277\336\263\247lV\332n?K\356\313M\005j\246?\365\\?Y\262N\306?\251h}\3675\032\266?\233\251?\307\013\231\311\277\205\233\003\010a\361z?\313\232\374\261\220\220\262?\"\343f\231\250z\270?\236S4\267\305\316\251?\023\205\360ci\340\254\277%\255*\002\247\377\246\277\323\177\253W\020c\307?\n\206qw\031\rt?\0242\341\257\302\253\264\277(a\024\353\356\372\304?\034\250\305\2307\017\247\277c\367\374z\001`\243\277\276\304(%Q\377\240?&\310jd\300\364\307\277bQi\355Z+\235?(\273Mt\235\376\273\277\336\225s\302\304M\245?\263\234\323\230M\265\303?\227\255\262\227\005qu\277n(\364\032\343[\232\277\200\026Z\346\221\235\237?2\211\342J)l\266?/{\341\300\300\361\264\277\246}/\200sO\304\277\010!b\253\320L\272\277zjI\271\321X\312?\345\202\237\350\026\247\275?\025$\251\235\366\277\253\277M\344\250\004\330\206\257?\271>\023F\237M\300\277\006\305(m\024\320\272\277$\374\256\007\264r\272\277\372\317\311\246/C\210?\017L\276\013\027\245\260\277\216\333\276\257\021\264\302\277S\237\007&it\207?\200\2626ML\270\241\277x(\000\216(\261\260?\353,\231A\377\343\272\277\357\240l\371t\272\261?1\203\340\340\353d\272?\273\002!]!\375\274?\200\375\222\346t\211\251\277$\215\364\0007\276\201\277\206h\365\001J\002\277?\241\232\024\254m\277\270\277\240\3414\312\371\335\262\277\212\035>\025\207\000\267\277GZf\254i/\322\277\360\366=\035\231\032\272\277\'\013\0253-!\237?R\t\360!.\341\226\277\217\312\024b3\343\253?\252\355\265\343\352\023\266?\276\004*\347G<\263\277_\336\326\231y\212\266?E\221O\n\002A\232\277\210|<t\247I\264\277\366\nvH*\037\235\277l$\363\203S\331\256\277b\306\365\207\210Z\216\277P\220R#\022z\310\277\027\"\031\353\301\272\201?\321\343f\317q\303\226?\033\301}q\314\216\302?^\325Yl\237F\314?\357\211\2730F\004\273?\324\262L\322YH\311\277@\226\005v\311\002\300?\275\2037;<\006\312\277p\010L\nF-\251\277^f\\\212\211\275\266?\226\204\005\007\202\241\257?AGC\216xC\304?\203f!}\205h\250\277w\303ls\266\331\261?\353ov8\275b\253\277\3730\266gE\001\251?\376!\320s\264\256\245?M\327\'R\033N\312\277\316\007\321\342\305\244v?\240\014\243A#\346\256?D|\375*\231\223\255\277\030\230\314\343\263\005\261?\317\376X\n\246\nv?\330\001\320CC\032\246?\343[\005\350\221\315\211?\343a\375\240\261\221\250\277+\375m\253\220m\253\277Z\033{$\001\253\227\277\270\331e\332\373/\272?p/;\325\026\005\256\277\230\240\371\221\277\375\264?\001\375\"\\\202\017\306\277\220\337\243F\317\343\263?\206\251+El\262\240?\341\370[\276\360\225\241\277\"\272\3226\330\006\246\277I\306x\025\375\020b\277\207W\353i\342\t\277?C\231+\355\341\357\221?\337\242\247\310\200\360\262\277\030\024\226\255\024\313\303\277hP\2569\030\247\251?4+}\230N/\250\277{g\030\367\t\007\233?\030-\313\214\330\"\222\277^\027\377\nN\355\300\277\267\367\377\352\242C\250\277\024+\214\344\277\332h?\277\204\332\316\365[\300?f\314p\000\213v\214\277\034\204NBe\316\257?D9K_\214%\263?\024A\177\351v\226\274\277\021\270?\372\315\033\266\277\033\014\275\231\223-\263\277J&\270c\017\266\231?\246\303\207\214\243\303\277?O\213-\256\276D\261\277\023\323\224\021X0\261\277\2266\335\276_\332\311\277\340\305T\270\222\276\227\277\363\311\002\2314\277\325?\304\363\007\376v\267\311\277\016n\331\000E\206\321\277-\353\306\304%i\273\277%lY{\211\240\263\277L\002\212\3344\256\222?\347Q\006\n\024\212\200\277.\004\312\317d\025\223\277\010\'\214\352\365)\264\277\362\205\261C\250\212\245?\267\237\254\257\355\357\277?\324f\177\267f\344\266?\245!\013\020$\376\234\277)ti\231\023\226\260\277\247\250a\023\020\203\267?o\016m\021(\014\267?!a\302,\265\227\267\277\017\206VY\335v\260?\317\'\344_\212@\251?Z\014n\240\225T\266\277\024\372\305\313\365+\254\277Fp\275/oY\231\277xE\362\351\323\265\313\277\335\027uq\216\020\255\277\260\211(K\027\367\251\277^\356\034\331w\212\224?:\025\231\346Po\261\277w\330\026\253>\311\251\277\226\357\332c\007\005\273?\312\300\220V\2469\244\277\023H\331\273{\274\322?r\014<!#\245\205?\357>F\021\035V\305\277\223\0176\3150Ti?s\2557\217}\313\243?\335u\374\236m\312\252\277\335X\375\362\035F\250\277\341\037\020\020\014J\276?S#\260\335\0033\264\2778E\221\244@w\275\277\377\246\000\245\367F\266\277U\322\241PJ\254\264?\340\211\274;\217\016\236?;\346\367\267\025F\275?\340\221z\213\215\370\251\277\362\n\206nd\000\273\277\007^\226\r\\\327\234\277\177\3033\004Z\252\276\277\210\3330ev\033\266\277\235\242\2621\331#\246?5\353o3.S\321\277FH?\002\357o\303?\007\262\\9\225f\242?\326\274\345\300\221\023\306\277\"C\336\312\317\237\320\2772\250\337\235g\272\222\277\356\'\245\001\333j\320?\026Z\276X\214\202\320?\317\212\254\341\274Z\267\277\323\322\324O|\341\235?\374&@\330,\024\215?\276\256\323C\3345\245?@T H+\305\263\277\267\334vVd\021\177\277\334\r?\254\232u\255?\354\341P\221\324-\325\277\013\351\273\263\354\006\227?TF\335h~L\252\277\037\2374\215k8\251\277_p\350\0062IF?v\344T\210-\241\276?\212\340\007\020\334\233\310?x\321\267I\344\231\233\277z%\225z`\024\246\277\230y\215W\324\231\242\277\275\215c=a\025\271?\317\321\024\326\366\264\264?\010\363q*%\'\267?n\351\021\332\036q\274?5\007\232\213\363@\246\277\006\032\2662\227\334v?\364\345|\271!\277\273?!\tH\332\310>\260\277?sjD\236M\260?\345\\:\315\367\375\237\277-\302\006\223\360\334\241\277NV\226\2608\\\303\277\236x\372\013\225\017\261?\377\372\033\300\233\203\266?O*{4\tg\275?9\217\361\206ZY\315\277\363\322\216\\\334\300\257\277fNU\352wN\234?\324\014\303\031\025\354\306?\262\226zf\257\001\237\277\222os\274)\326\261\277\r\232 \234\221 \265\277&\230\242\334\343\375>?\241R\014qwz\251?S\233\335+\271\340\265?a\261@c\241\323\303\277&\276\250\272\004K\246\277!S\234\3404h\272\277\256|)+C\212\267\277\002W\373\266P\200\224?\222\362\030\352\255\251\267?\341\334\262\217\006\205\301?J\207,r\303\241\253?\020\326\366\362Q\315\237\277\270/*\030-\331\230\277\263E\336W\240G\264\277\320i\021\361j\320\256\277s\244\232\247\003\217\220?}\213Y[ g\261\2770\233~\301\013\346\311\277\264\203\006^\362\351\265?:\244\320$\364\\\245\277\203\037G:\334\265\243\277\364p\004.$}\236\277H\3363\016\244]\257\277cP\365\352?Q\303\277\003>/\177b\035\267?j\r\024\233\t\337\244?Y\245m\215\016\230\242?\226\2043\254\301\320\272?%\352\301;\272\313\255\277\004u\311\373tU\302\2775K\355bS\340\302?\374K\376\377\202\200\243\277\375,\242\233\000\262\233\277g\205\352\374S\313\300?,\231\216\301\262\207\272\277\205\257\037v\013)\304?\t\230\331\321\345\201\267?\021\000\232\377\370\277m?\230\227\322\225\202\324\307\277\371\366\002\215\345\276\235?\233\025\334\177 \260\226?\260S\177\370\216\010\277?\002\320\205\212\217\205\205?0Xeoa!\255?\206\261/\201\213\267\302\277\224\341B\247\371\314\255\277&\364\353\271\213\345s\277\300NH0\335\027\252?\340f\003\013\014\025\027?\001\223\001\276\203E\263?\346\231m,\303\300\263?\002\017j\322\353\262\305?C\017&\3307*\304\277\365\375\t\357\211\375W?W\351g\013\267K\264?T)\036\335\242\351\264\277\304\270\215\0201\352\247\277IK.V\006H\267\277\226\322[k7\302d?;\345\025\266#\320\274?\001a\227j\203\310\244\277 b\272>}\375\214\277,\312T\0070C\237?i\220\016\361H\377\272?\362o\2509\263\223\201?\3574~\306\373o\256\277\203\251\240f,a\265\277U~\031\245\014L\222?(\276t\274\216\304b?m\237d\236\021F\272?+P\004l\275\330\272\277>\t\206\376{*\237\277\020\347\3026]D\240\277k\305\265\"\301n\313?\t\013\0218\006\326\261\277\340\331\263\374\'~\230?\023\252\243\300-\312\301?5\035{\256\000u\263\277\325\r\000ejgj?\254\344\360Z\016\244\264\277O\312>\256\305\203\242?\346\240\342\221\376\270\271\277\254\346\243\225\035E\220?\363\251Q\177\247/\262\277x\324\003z\242&\266?\352\330\247\365\352\253\236?\257`\210\205\337\302\317\277\004vvV\266\310\314?\374f\356i\317\3701\277\377\231\267\223We\261\277\311\365\273\357\373\227\224\277\031\0067\010\223\333\304\277I\333\204{\201\312\207\277\306\346\353#\252M\222\277\354a\270\"\013\234O?\016\300\376\220\177\340\270?\357\374I\035\322\250\263?2{K\276\224\223\232\277-{\262\206\353\246\250?@\276\256\030\273%\313\277fr\211\226\273\300\275?p\210D\342\001~\277?X\315\034\340\210n\262\277L\324u\346?\346\215?6A~\002\253r\260?O\367\227c\361\322\275?UA\220\256t\n\264?\340!fP\321\032\271\277\274\304\205zy\025\247?\336\334\035\367\010F\222\277\325\367AXw/\247\277\206f\377\334>\224\272\277w\n\304\005\214\302\222?\037\344\330\276\263\036\263?H\023\235\3339\257\313\277UK\277\266\004!\274?\023\245\303\327\301\010\244\277:\213EQ[\302\213\277r\321\0345\340\335\274?\027\235\316\235=G\250\277\275\270\234\027\200#\277\277\216?\273\367`O\264\277\346):\215\355T\260\277\242k;\037\271`\261\277\r[G\221\364\253\266\277\246\327 \316\273gw?\342\245\022\345g.\300?\004&\332\341\253\273\251?\216\017Jna\370\277?]\000@}\366i\242\277\307\214h\375zq\251\277t\365\035\333Z\315\233\277.D\251\224\375\017\275?B\016jv\304u\244\277S\311\rq\360M\301?\337\257v\262\212~\250\277zF\301\364xg\306\277\355\313j\325\n\326\200?\002chK\024\227\215?\311\225 yX\203\316?.0\221\313\252\317\265\277$\347\035\004!\206\276\277\210\177\325JW\236\301?<\024\317\247\364\024\262?\325\243H\341P]\250?\265\360IV:\242W?\326M\305\204\315\275\246?\242\367t\017\225\326\246?\354+\004\026p\202\252\277\332\222T\333\230\355D\277\337*\356%w\t\303?\\\336%\360b0\242\277i\333\032\230\n#\226?\014\352>\372y\032\263\2772\010\312\256\375\207\251?\010o\366\212\022\214\231\277\253\233\265\230v\335\271?r\247\223\300\346\"\305?\261\215\'[\231\253\224?P\032\275\342&\302\267\277\307W\2445/\013\255\277@\364\272X\266\032\260\277\376:\3328\223v\206\277V\366\364\334\232\025\264\277\320y\232\315\352\367\300\277\221vm0\251\377\243?\362\354\006\005\333m\266\277\327\245Z;\024\214\237?\222\360+\277\332\r\226\277_\227hQ\302\333\245?|\2329\323\212\270\300?_\233\216\245\020\262\226\277bx\022\277\365\263\255?n\014g\ti\354\304\277\2508\023\247\270cW?\332\221\335-\017\031\247?f\220\226\370\177\243p?W|\021\236\031k\260\277\346\0267\023X\213\332\277\20019\325\325\024\233\277\346\254\327\232\243m\270\277\006\305\331\223\302f\300?\025(\272\006\3127\307\277kV\212\325\350\251\303?S\001\226eA<\253?\312\010~\327\026N\232\277\035\362P\003\302\006\261?S\204\000\342O\221\253\277`q(\375K\362\243\277\256\021P\013F\212\252?\r4l\207\314\341\266\2776\226\253*\0065\243\277\255\262I\304\344\223\276?U\346\033\t|\371\267\277hHq\341\235w\255?\201\362\243,+\236\323\277\256\204\035`\000\034c\277H\331\315X\211\245\242?\317\023\013\331^n\262?L\025\023\343\207B\262?Q\276\260\002\3630\226?\013\337\223\245K\366\244?;\364\"\364\220\261\213?\244O\262\017\231\261\257?6\033k+\307\312\255?\326\326\231\365\335\320\277?[.\347\316\371\323\332?\267Y\030\205\356\266\274?CI\353h~)\245\277\260Jn>\343[T\277P\233\243\376V\264\222?$\351B\362[e\264\277CW\227&$\320\306\277-\327\245\005*\366\325\277\263\346\025\314\276\215\267?\215U\274\316M\364\246?\224\n\247\301G\324\261? \027\242y\330m\247\277\205\345-\2476>\260\277\356\033\\]\244\362\265?V\022\262\232\300\263\243?\253\207\323\263\205\270\271?\033\372\237\027\272]{??\005\010! M\315\277Kpx.S\334\216\277\206\325\324{\353M\271?\336\275~\002#x\226\277\251[0U\237\234\265\277\024\315^\363\310\026\264\277/\205M\246:L\245?\023G\r\322Nk\205\2776n\033}\256D\277?\376\021\005\331\217\304\256\277t\021v\026\021{\277\277\253\366d\367\243\266\217?lkJ\266\310\325\257\277\224\301\343\345\207\321\271\277\343\260F\245qw\267?\240.\004\255\350L\224\277\242K\356\355\201\267\255\277{\215\014\351\337\257\322?\257\217\014\020\211\364m?\320\322{\312k\330}?\265\337y2\214\031t\277\3552\240\021\231\005\301\277\024 \261\260\237m\227?\250\364\256\362}\000\237?\336%\340\357\313Y\274?|\025\'E\3266\277\277\026\027\010\024\324&\235?\216u\201\330\263K\261\277\r\265\240\304\302\374\241?\272\273R\310N\315\244\277\035\002G\016N\233\303?\256\363\241\215\320$\233?\235\244F.\323$\322\277\244\002\233\203Jo\246?\373\364\2653B7\275\277o\203G\247\201L\251\277\2672\274O<\035\277\277U@\031(T\024\247\277bP@@\236\217\310\277~\311\022.\273\302\307?\272\035\334k:k\233?\241\201\201\325\213B\276\277KA\351\240N?\311?\232(\n\234\314\312r?\2535\032\374\016\006\221?r\261\254e\365\222\260?\213\220\013\032\022~\266?j\374U\217\372\271\203\277w\323\327\243_\345\220?\\H\363\357\313>\250\277\252\030dN}\355\224\277\226DN\270\314\256\212?b\222\214O\312\336\267??\201*T\364\355\242\277P3\331Cr)\267\277\316\226\216\374H\371\301\277\2105\230\203Bs\274\277\362*\217\307\320\367\226\277\361\312\233\344jQ\207?\177\355ES\242@\220?\027\206\351\377\302\227\272\277\225\230\305\265]0\244?K\210\"\262 Q\276\277c\213C{\274a\271?%N\313L\241\265\257?\311\373\360\235=\217\245?\233%v\006`\255\310\277\336j\010..W\201??Kd\350qs\207\277\242\200*S\261\032\257?U\366\235\005\031)\240\277;\366\010\274\330\017\265?M3Pm\021\313\275??\354\226\033\253T\243?\272\332\3638\265\032\316\277D\224\021z}c\246\277\271\372R\332%\026\261\277\234\302\026\213\\\232\224?\374\340\336Z\227\320\272?\201C\024\346\341xU\277O<\204\215\005rh?\010\000\231n\030W\203\277\241\342j\004\013\270\235\277\232\264\316\206\304F\220?8\221\035\211~\235\213\277\300\326ad\023\201\220?I*?\022\225h\260\277\336W\036\235[\271\317\277e\034E\205\227\221\201?\361\335C6%Q\225?\347\222\\\374\245L\274?7\374E>\245\205\263\277$?\213\376\3633\257?G\351\031\306\324\215\203\277\374-\240\305I2\322?\276\342)u\307\307\301\277\254\270Ht\256\307\252\277\n,&\253%\307\276?\010\230\243\274\211O\267?8\333\374k\362\246\211?\214\322\244\275C?\217?\261\006\351\327\326<\313?L27\016~\365\312\277f]\336\204U\305\204?\311\222Tu*\300\300\277a\363\250sd\272\260\277\265\235\276\300X\252\263\277\234\"\0232*\304\261\277$o\210o=L\305?\203_\rsoKf\277\321\205X\004\227n\257?\340\315\377\321nN\256?\257\237\252\031*\020\315\277K\356\253m\225 \241?\264E\375TY\216\261?W\304.\273\261na\277\031\260Sr\277\247\301?\365\331k\2279\343\300?\252\013\031\324~\367\272?\366\003\216@\014\362\227\277!\362#\204\241\372\262\277\206\361@\252h\273v? \001\250\024\261\267\270?\337Gx\"m\236\301\277\307\325ov\031\376\254\277,\373\021\344*\026\264?\207\035\332\034\207\260\304?n\316\264=\262\265\224?\323\330\345e\372\235\227\277\030\206\320\232\211\257\311?\311\331\275\372jj\226\277\032\323\316\277\303\332\260?\323\257d\261\002*\273\277|\235\350\203\351\236\300\277B\351C9\021,\236?\325*\000\350m\263\177\277*\030h\315\354\371\301?W\314\236\221\333\374\267\277\371\316>y?\241\246?a\322\353\315\266\234\277?\021k\375\324\354D\306\277\025)\\\346B\341\270?#\337\036\005\007_\270\277\355\364\256\200\241$\245?\026m\266rrS\330?&(\232\037\250t\300?\347\'>D\233\367\271?g\273\334,x\233\267?\266\271K\r\363\276h\277\325\205\375\234\370{\250?\200\377\352\246\211i\252?L\247\323\267\264&\233?\005H\365BK\256\241?\301\030E\302q\330\262?\335\251\346\366\024\207\275\277q\302\210\361\032\'\220\277\236\000\253\021\233mM?\375\t\254\026\373\370\266?\200\336\275\274|\264\262\277\022*\346Ofu\263\277\033\352\250\0137\225\240\277/P\224C\022\376\266\277\264\233\254\344c\307\217?\253o\267=\213\346\216?\224%\037\031:\266\244?[~4g\337\265\230?/(`S\340\352[?\306\306\300~q\323\225\277\006\n\256\234\242\312\275?Z\026E\224\344\373\260?9o\217\373\326Su\277ef\014\300e\363\246?\353\013\301\355\261\013\263?\253\351\222\n:\301\266?\236A\365\316\306\000\270\277\310\304\274y\2303\200?\036\\\341\367|\241\276\277\235\273\036(Uu\260?\036?\323\330@\177\251?\245\326}\010P\344\246?G\271\262\237%F\221\277\316\tP\211\022m\224?\3703\365\240f\240\300?\235\351\211\253\033\370\260?SX\347\214\203\036\303\277\204\302f\257_\376\323?\316\231\374\320\324-\216\277\327\014\206\261\307=\245?\003\206\'\370\224\017\310\277\3735\017u\303\036\302?3\301\300\305\360\353\304?\316s\235\037&/\254?*Bs\030\200\332\217?g\'\026m\211Ax\277\211\222\226Y\364\222\246?\021\276\030.\245\216\254\277\312y\236\013\246\244\255?\327\322\206\024\013c\234\277\236\027\235Q0\334\217\277\3458\014\177\371\300\276?\032\342\232\n\2362\311\277\027\273P\323\206\"\244\277\022A\230\242Nj\203\277\330\"\216\276\201 \234\277\254\336\017\221+\364\260?\007M \236K\261w?\026\300P9\010!\245?\3053p\020\270\351\257\277\357\373t\271\213H\213\277\362H*\351\004\177\211\277\233\252\240\201m\215\321\277\316\0362\036\022\343\271?R5n\016\207\274\260?\010\216\017\224\325V\241?V`\002\337:W\306\277\347<H\270\334\351w\2771\217#\026=\362\250?Z$\000-\263D\230\277\250\265<>\215\366\300?\306t\217+\\\n\265?\375\303$0wu\261?\216y\032\274/;\316\277\327\326\324\014#\231\272?\203z\200\376\236_\266?\273q\271\313S\033\251?\333\363JY\3313\256?\334hi\223\222\272\301\277E\373P\372\211\232\240\2779\251\"O\306\200\240\277\030\253\336H\304a\212\277\330e\t=\221\326\240\277\270\223\2061\250\010\240?\342\2101\351\3527\275?p\224=\027\014`\245?\222\346S\244\366\002\220\277<\335\347\217<c\275\277<\256$@\017p\227\277\360\017\323\262\227[\256\277\343\372\010\215\224:\303?\275pb\025\027z\243?8\032CHX\237\275?U\227P\371\373\027\204\277\307r>\3459\206\255\277sp\352bwz\240\277\325C\246#\270\361\244\277Z9Oik\225\265\277Fa\355+\353\275\241?\232\352\324\026\273x\277\277\301\350\354.V\t\242?\233\374\326Y\205[\265?\310\3170\324\022\335\261\277\021\363\210A\207\026\263\277\320E\231\370\310\341\273\277\026\313A\327\271\t\256?\223\022x28\001\312\277/{1\345\306\260\317?N\306&\365\037\025j\277\004\333\345\0343\370\306?)9\377\177\353{\245\277\300\\\255\337\370\304\305?\357\037\335\242\016\305\241?\210\211?\310g\357\211?1\253\204\362,\377\236?D\375\335\321\374\222\277?eX\277g\341\305\244\277B{\253o\357\345\256\277\315\200+\257\325\265\310\277\037s\260]\216\314\227\277\343\221\264\200EQ\241?\233\365\255\241\254\335\244\277o\006Ct\235P\323\277\307{\211\262Uc\220?#\353\344\314-\376\253\277\334\323R\342\326\260\260\277\312\376\203\\\201\375\302?\325\301u\337\014\334\240?3\210\"\037C\300\272?\303I*c\'X\306?\254\302o\"U\037\235?\314Z\310\362\242\312\265?\226s\360\003\372\254\264?y\374\305\242\330\231\256?\3624\226\307\224P\243\277Z\252@lH\273\315\277\360\325P\272B\261\255\277\227\256N=Tv\273?\312\213\0056F\332\310?\\$\225\374t\t\246?I\346\230`\311\213y?L4)p\020\202\303?0\361\036\t`\361\275\277\023e\227\273o\"\233?\233MLz4a\250?Dttk\265\230i?[\314|\215@\327\264\277\314\223\271\272\254\260\201\277\2371\207\036\277\211\263?\026X\356+\331\243\222\277\221\250\2453k%\300\277\222_)\202\313.\224\277t\3302\221\020G\247\277j\364\305)\233\'\265?\351P\230p\006E\247?8\024\206\230w\362\244?1\247Y|\327w\271\277\226\330f\364U\331\320\277\336\313\270\035\376.\212\277\343M\255g\334\234\244\277\211v\354\263\307\354\311?F\2173\345Nf\303\277\215\314\354\227\n\224\304?\361!\242C0`\320\277\265I\020\005\316\000\323?\251#\270\267)W\305\277-\036\211;?\255\247\277\027\210\330\\8k\256\2778E\271\333\216\327\211\277\366\307y\366tv\230?Z~:\221\006F\240\277e\002\206\013\327b\312?z\302)\225\017B\264?y\206\274\320^\030\212\277-^\246\210\341\325\226\277\251j\326\367=\303\302\277\234\210\231s%\030\261\277\212\'\372\333\216b\305?\272C\260!Q\027\240\277\341\244<\250YW\243?\302\342\330\031\255\202\267?\331Xjq\017X\220\277\311\t7y\346\003\205?\003\356^\004\267\274\242\277\342\324\3505\035\006\267\277\033w\202\323\237\227\221\277\232\037\310\261\326\244\261?\266\264\007\324X>\262\277\0134\334R\266C\264\277\2230\n\362\024%\204\277\220\341\234,u$}?0\377\356\033\363N\312\277k\033\357\212\377v\241?\'>\001#W\215\260\277\350+\340*=\327\256?$\374\003\265\302\207\245?G\300\236\225\010\022\245\277\343\361\241\024o0\205\277\017H:\334\'\t\235\277~8Q\305\262\034\275?\263`\334\005\324\250\221\277<#qg\007T\246?\315!^\355\205\206\244?J}\004\3562O\242\277\006\213\375\374Y\330\211?\3670l$H\004\265\277\354\351\023\274Q\251\265\277\376k\320\315%FP?\332*A\327:=r\277\201\003\317\302<L\304?\215#(\344\013j\265?\321*-Gbg\227\277\315\r]\314L\374\256?\213\335G\3332w\266?\0030\226\016d\270\215\277\263\361B4\356}\255?\0311\273\234tA\262\277M\265\374\366\224\"\270?\235\253u\312Ov\267\277\241\320\325\327ef~\277\004\220\374\324;Z\305\277\2626\3001x\263\305?\230v]\rn\337\246?\206\340\271\274\353\"\306\277qo\351\344s\014\263?\211\261O\224E\214\267?^f:\221\371\302\251\277\275j\354\203\271\347\231\277\264\324N\010\330o\213?\377s\271a\222J\266?\214\310\307?\354\262\317?\206m\364L\236\206\236\277\226\240\243|L;\262\277\246\360+\254\022C\321\277\277A!\342F_\313?\"\327\364\353\017\030\236\277\306Z\324\334\213:\300\277+\257\373\311\200\270\264?\024\267x\344\363\t\275\277\27779@\311\024\243\277Iw\240\304\330\305\273\277\314c\302\035[\224\206\277\212\025A\255a\024\266\277.wd\224\2746\235\277\000\333u\246\010\252\302?\026\307q\007\312\374\202\277\3619\351\223\261\212\253?C\353\215\260\031\225\244\2778y\3551[\337\316?w\277VF\371\t\260\277\027\227\0071W\357\310\277\3534\343\315RX\311\277\tM\271\236.C\306\277\236\353\223\037\266\205\255?^rM\301\317AU\277\202\355\206\244\\2\240\277\236\311}\357\213T\221\277\271A%I\230\\\264\277\001\242\n\216\243f\300?\"\035\t\177^?\217\277\302\027\266f\314\037\251\277\303\333/0\t\032\200?\333\377*hK\222\314?hBt\247M\265\260?w. \233K\004w?\337y!3^W\246\277aTyH\357-\273?M(^\204\244\035\245?B:C\264\240\242\201\277\307\327\261D\003D\263?t\207\275\355A\216\250?=\n\324\023P\260\265\277]\025%\367O\271\313?\034\032P\355\252\205\261?Q\260\017c\360\246\262\277s\230\322\034I\317\306?\355\004V{\2368\301?>\260\333\004\250\310\235?\316\0070\233\367\272\224?3[D\254n\344\273\277q<\232\276/F\227\277\204\255\370\264\264\215\232\2772\027\250=\361q\274?!\007&q\324\334\276?\367\013K\250\300\272\245?\240\313\311`\374\200\220?\005\276\373yV\217\226\277\256\216\233\267\363\'\242\277!\224?\233\032\237\235?\005n;\250\036=\302?>^)\350!n\322\277H\260\341\366LGa\2771\010\204\005H?k?e\005\301\213\036/\301\277\376.\244\201\322\344\256\277|*\257\021\247\r\264\277\314\263y\221t\262\252?;\276\r9\275\275\312\277\366\306c\257\340\313\206\277t\313\017\004\263\361\223\277\343;K\3704\264Q?\261?\320\206\343g\253\277x\001\3325\306\215\273?g\336\0073&\330\260\277d\375Mh\210\032\266\277#\373hf\252\361\254\277*j\033\232\273\316\272\277\030\2115\21412\226\277\324\311\354:\354\212\250?\326\375>\313\013\031\244\277,<\257\362\014\326\265?\351_Wn=\316\242\277nyY\304G\005\220?\341\245\203Kn$\255?7\177\305\002\331\006\273\277kQ\251:\352c\264\277\353\322\213\337\030O\260?$\327F\323\202\305\310\277d.\305e\000\243\220?\220m\"\255\322_\240\277[\032L\031\212J\223\277\321\212\316R0\244\243?\336{\016\237p\232\242\277\327\005iZ\274A\223\2776\324(\366\334s\303\277\353t\250\030U\016\276\277lb\333z\306]\261?RR\352\377\353`\207\277\t\310\251S\277\335\301?\017\362\240n\t0\222\277\377@\030\"\017\203\226\277\252\256\25240.\323?IE\355\201O\023\273\277\373jKF|I\265?YM\2541\034g\226\277\265\037\316\255\233T\217\277>\'Y\253\244\356\233?\201\302\342G\375\273\260?=\0167\322\233\000w?\354(\340<m?\274?\307\366\037\222\242#\231\277g~\276\355\307\264\215?\373!\177W\236}\304?\220\212\352@\013\017\271\277w\245)V_\336\232\277\356l\322/\276X\264?\266Z\031\r\t\232\306?\224|G\026\324\333\257?\265u\307\257}\356\236\277\013\366\037\003*6\220\277\344\002n1\352J\224?\3562\261\350\"\212\260?\256\221\277\255!\273\227?\337\371\273F\363\026\312?g2\332\034n\000\255\2772\361\360\036n\004\245?\341\214\032\307b~\321\2774A\033\303$\262\260\277\371\024\326\223\250I\255?\264\307\353e\236\316\257\2771\r\024\\\277v\302\277\204\001\275\202\026\232\253\277\004\334\214\327\276n\260\277\354\3346\226@\006\273\277\032\352b\202\221\202\260?\346yO\214\036\177\\\277\330X\240\330&ql?\035\217\202\340\245.\253?\346\361,w\004\342P\277\374L`\237\202z\251\277S{\331\333\361\322\256?\333\037a[\027\002\266\277\304lW\361\232H\245?\004\027\210\3146\213\213?\035WtE\234L\244\277F\267\233\357`\221i?\030\032\323,\324\344\250\277\370\320=Z\251F\251\277\323_a-\251\207\270?m\024h\357\312\212\263\277\205\211m:W;\210\277\344D[~\300A$?4R\006a\301\'\245\277\367\261\317%#\351\313?\346\250\367^\313\337\261?\320R\267\354Q\036\256\277\250$B;\371>\227\277\274\371\367vz\224\307\277\375\374\340\230\333\177\310\277\222\305i\005\037\365\265?\r\371\030\224\270/\257?-p\314\n\r\021\266?\270\315\034,\255k\221\277\240R\273+\354w\220?\207\206i\251\207\254E?\txi\353\222D\220?j\271\341\244\0201\246?\375v\3244?E\253?\364\255\372\264\251\320\267?\325\266\315\352\256*\201\277\200\357\203\215z\223\272\277V\312\333\257\364\350t?\247\021Tp\224H\177?uE\262\373.\014\257?\232\373P\007\256\357\275\277\022\340\251\230\365\247\264?\213\006\033\3423/\236\277\303\252o\270~ml?|\364;\237\315\244\274?I^\003\211\213\253\274?\003\rV\353m\200\247?\266\254@\035\322\313\272?\021O\003a\224\247\261?a]\312\272s\377\275?!\323\303]\264\353\273\277[\351U\343\316\251\244\277\236\366u\214b\242\263\277\335\271\230\347\323\337\212?\221C\300\245\231+\265?\376\3702\324\263(\303?J.\303\324g\"}\277\333\034\3638>\346\252?\305e\300\204\356\241\251\277\343T\230IY\372\262\277\252\223=\214\240\016\300?\177GPJ@\032\275?-\263\335\221\347D\272\277\321\300`W\351\235\301?W\374\365\361\363k\303\277e\244j\001?~\246\277\332\275\361\301\310\207\327?\342Z\372\002o\316\251\277\240\354\366}\264\273\244\277\252HM\320\ns\225\277\221\010\202e\343Uy\277q+b<n\316\242?\\\003~\325\000C\224?\007C\345\233\260\375\277\277\266P\375\334\257j\262\277\357\353\377\220h\200\230\277\013R\372T=\215\325\277T\200#\203\320\307\264\277#\334o\257\351\006\277?\374Ed\310\2005\303?\366%I\026M\277\216\277\255\266T\210\350q\266?sS\355}\314H\270\277P\220\"Ad\272\234?\0277\371/\246\227\304\277O\267jw2\330\270?\0144\354h\030\322\271?\324\016[\030>\373p\2771\366\'\3122\220\302\277)\002^\307\001\013\231?\367\n\014,\211m\267\277\177\030\026\023\2460\251\277\243\364\013\037\252\233\240\277\364T\366!Z\236\275\277\302\370\'\034RM\304?7\340\303\033\261+\272\277\033c\226-\340\316\260?\371\345\350\023\243\230\275\277@X\210\3277#\273??\367\261]\316\217\271?\365rM\352fZ\225?\276\250\271\\\032\020\310?G\3561\271c\024\274\277\353|GJ\345\251\251\277\331\275\375\0175P\301\277\272\367\325\344\234p\215\277.\2627\341sF\226?\031\371\203\302\010j\265\277Z\010T\016\031\\\276\277P\200\030\261\320\246\252?r\363r\247\224\013\225\277\244b}\300\337g\310\277[\201%\002\347\017\271?by\371\273\322\314c\277\233\005Y/\036{\311\277\2035\207\344\367W\253?F\034@\362\306\226\304?k\177\372Z\027\236\263\277Qk\030iM|\266?\275\035\207=\230\331\257?Q<\317?\t\270\261\277.\036\203sXOv\277\032\005\357x\316\356\303?\2728\006\033-%\213?\033\rW\266\352\240\247?\377\224\036\030\030\332\252\277(\340m}\r@i\2775\333\314qW\340\217?MR,o:T\177\277 \246\360\254G\257\226\277\372q\005\347\252\232b?\0373\367\224^\345}\277\030\225=\225\022\240\271?Na\306\211\361\024\272?*\007\370qj\200\303\277\242\037\300^\223~\251?\006\311\203\230\232\345\245?\227\320\340\343+\201\300\277N\213\'\265\032\223\234\277p/\"F\237V\251\277\366+\370u\022\032\203?\230E+b>\"\265?#)\322.p\345\300\277\276h\3742\212\321\276?I1\2075\032\325\250\277:\366\336=\323>\225?C8T(e\252\267?\034H\002\310\321V\220?\017\217\340\366z\331\213\277w\037\235\036\320Y\270?R\251\006\207\243B\273?\305\344\301\237H\203\243\277\320\235C\n@]\262\277\374&o6\r\256\256\277;t\322\375\326v\263?\245\241r\244\252#m\277\315\030\273Z\037.\314?\226L\256\274\2544\274\277\367\316Q\003\231m\277\277+r\010ri\315\266?\373\3053\274\254\251\256\277>\346\376\240\251Y\214\277\261\022s\035;\024\210\277\026P\036\001\032\253\224?\315\0310^\346h\200?\261=\227-\010\014\245?\373{`@2z\275\277kx!\001k\020\233\277_\200\013\255\023\351\231\277g\233[@-\027\257\277\022\361--\242\035\251\277\211\211\235\315)\252\301\277 (\317\373\351\205\260?2#`jK^\244?m\274A\363^\312\303?\026\331\310\302\374\007\242\277[\205\212\353\350j\272?\255\276\352\n$\343\310\277s\275\334\000I\304[\277=\227\340b\204;\222?tTM\005\227o\264\277 \347\026Iq\372p?\371\0361:\367\257\274\277\366w\377~\262j\225\277P\335\321\003#\210\232\277\272\235r\315\2661\206\277\221l{\017|\373\257?:a\362\000[|\255\277\035e=\002\322\332\265\277\013\017\251\367\377~\216?\tR\025\262\001\243\262\2773D{\350J\331\320?\252\243\000i\315p\264?\256L\326\301\254v\253?2\032a\203$\253\231?fag\370%\017\265\277`\236~\351\340W\243?\246\361r\244\021=\251?\005\301\215\215\237F\220\277Zj\000\325\332\366\271\277&\373\345Qq_}\277\237\235\031\232\320\031\245?\362x\236\203BH\330\277\211\006\250xXX\265?\222&\332nL\231\246?\240\330\034\316\323%\222?\364w\303\252\366\037\327?\305\n\357\370\302\023\252?\346r\247<Rw\243?l]\273?^d\265\277\254\223\311\300DW\257?[2\371\351\267\273\207\277\301\2479W\203\246\266?\257\321\325\276m!\265\277\005q\345I\353\247\257?z\341bDb\377i\277\245\342&\225jg\264\277\033Ts\037\005\360\240?1s\320\304;\335x\277\256%^\231\276\306\221\277\022\0031f+|\310?.I\'\n[\252\271?\250\016\332\213.F\247\277\177+3\007a\264u?\250$q\230\033x\267\277\300\177\257\240~*\250?\262\266`L\314\320a?\313)5\366l|\300?\323\032\201\037\332\370\243?\000\27768\276\034\227?\345<\022\256\373\273\252?B\237\362\306\224E\242?\030\220QD\216\273\264\277_\263O0\354\315\275?<L_\362\241\242\322\277i\260\242n^\335\222\277\023L\240H\346G\265?\272\320\256\001\312)Y\277\017\010\3642$\242\263?AZ\027\263X\211\264?B\n\001\364\013\226\227\277\3726\206\325\214e\325\277\270L\355\200\203~\224\277\032S\3125\331\335\033\277/\301Y\005&\032z\277\341\234\277\375*\271\222?\315.\335\314\377]\306?\262\365z\354\202=\307\277\342 Q\257\321\207\314?4\013K\366\313\355\301\277\207\034D3\260l\246\277\357\034\355gq\000\273?-\360\323\301W\306\262\277;\262\001\325*f\251?\351\357\270\330\035&\271?\021ek\317\024\"\310?\320-2\036\310\376\210\277\204\243\322\220\3544\256?\t\254\352n\226\022\272?\313+\360\3407q\244\277\230\231 li\267\274\277\311F[hs\342\242?\340\273\271\302w-\255\277~?\034>\003C\224?\260\225\016\214k\034\251\277\237\307\330h\327\251\303\277\363\0324?\006=q\277N\027\267\202c\200\264\277H~\316\035Z\216D?\266\375\032\300\204\021\313?r\005\035\r{D\216?\325\206\213\236\342\366\250?\033\220\177[\235\362\267?\377\204\335\307\202\236\245\277T\237_\022-\304\242\277lP\230\016\300?~\277D\316n\007\220\331\253\277\023\027s\203q_\201?\306/\022n5\257\241?h\311Oi\324\334\262?\304\'\315\016\256\244\240?r\235\3768|3\262\277\\\026s0t\252j\2776\3649t\3745\270\277\343\343\217\260\350|\252?\214\230\207\365\322B\246?\374m\022-[m\271\277\332\306@\332\340\237\260\277s\300\267\3664\306\217?Q\226\177*\030fs\277\266\222&\364Q\301\264\277\350\374\207e\3540\261?\0065\211\324\316\363\301\277\230\225\224\020G\r\303?\366+\007\232\003\333\260\277\2611\317\307B\376\206\277\332k\272\257-G\301?\312\355Jri\273\254\277;\013\275\304R\244\251?H\305\267\001\356\325\200?\2644\253\357l\226\306\277\016\360\301p9\351\301\277)3\230\255\275e\271?\177\002\350\246\231$\245?\320\002\273.q\360\275?7M\332\3771m\250\277o\304\006\207\235\270\254\277\340k\'\001\306\205\272?\002\303#\2775\361\230\277\351\332\026\303\243Y\224\277Y21\334\302\305\310?\204\010\3630\016y\241?\350n\363\026a{\240\277\267\372\240K\345\311\301?g\003\2712h6\274\277{\256\226&|\311\231?\323]7\001d\240\253?~\310DF\312a\204\277\212\032d\321\276\311\221\277\303v$N\001\232\262\277\362\355\270b\220B\306\277l\001X\322\032*\305\277\276cuiep\272\277\244\'@o(y\265?\351]w\366\334\322\313\277\270Zm\205\261\203\261?\250\276\230J\246\020\301?S\3525\347\245EO\277\351F\270 c\"\311\277\377\345\016p\313\316\221\277\327O\014\333\363\235\214?\345\314\nd\217\206\305?RkL\023\221F\272?\355\326\226\351\253@Z\277\010\355\030\3659\251\275\277Z\374!l\016q\253\277\204\351|\351\230E\247?\371\024\324\000\277\301\302?\003\332\231\257\247\273\223?\200Q\304\313sM\261\277\235\311a\250\344\311\252\277a!\274\303\260\306x?\272\331\3021!?\223\2770\263\203B\021\204\243?\275\346S\277fp\262\277\276\016\025\374\234d\312\277P\362\352h\353\345\314?\276\016b\374\257J\264\277\326\271\002&\000B\273?\327\307D3\035\022\315?\367m\177\244\241,\256?\262R\213\024\340s\242\277\345\230=WH\211\261\277T\360\332$\221\024\315?\304+\373\212Um\243?e\223\030\031\367\322\303?\333\231o\241\032m\271?q\312\224\274B\000\246?\006\271P[\342X\321\277\010\232\330\0027\323\205?+a\256\362\222V\246\277[\336\022\252\013yk\277#c\334\260\376>\224\277J\24482<F\313?\232\261q\022\033\225\206\277\235\341\376\332\017N9\277\270\200\243\203\021\t\231?\352\364\364\345\313\'\332\277.\260N!\367\273}\277\022\016H\347\244\243\267?)\021/\371A\270\301\277?\r\003\312\305\312\262\277Sb\035\323\260\323\253?\237\376\323\023\302\276\267\277v\316\223h\013V\317?B\207c\0011/\202\277\224\231[\247P*\241?\221\206\241\264/\337\263?J\374\0011\334\253\261\277Mu\005f\337\325\245\277\216\2671\202\231\214\221?Gi\371\306\362x\313\277S\252\000\'\261\356\263\277\324,\346\231\310\206\252?p\341\345\264\277\245\240\277\340\302\257\326Zo\217\277\275\322\"\224\201\273\267?\231\302\347p\225\225\253\277\330\241o$LB\301\277\304,c\013\233k^?\250\320]J\035d\265?\306@\"s\321\354\234?\n~\023\343\374\333\300?.Gt_zu\223?\326\316\037\331\177d\216\277\333\225\021\376\336l\212?E\307\311\276\373n\256\277\021\337\177\207\274\250Q?\267\362D\300\003\325\260\277\240\270j\005\253\362\240?R\275\266\202\025\234\316?\274\215g\032\375\225\303\277q\025Ry\231\001\322\277\004\245 ]\376\343\247\277\213\006\265\014B[\270\277n!\257e5o\261\277\300J%\016``\271\277\rk\224\345y\356_?_P\223\242\226\310\323?\017\274\023\027\327\233\303\277\030Z\325ux:\227?n\373\035.g\370\225\277\315\243\221=h\306\273\277~\276\347+xh{\277\005e\266yy\312\266?f\253\001L\333b\205\277\313Y\302\000K\243\315?\177N\026\301\374\245\220?[\266\333L1\242\303\277(\031\251\251\355,\302\2779\255\025\007\222\223\310\277f&a\334\353\014W?*@\021+\351\257\216?\373\360{\272\204\201\321\277W\225\\1\272H\267\277A\313,e\337:\224\277\347\257\007A\303\225\275?zs\251\261\013ls?\037\311cS\021\271j?s\313\r$\303\n\277?\\\231\315=\327\336\266\277\214\260\337\n\300\271\225?\016*\352\305\230K\303\277\232\214\340\336jw\275?\235\270\332\325^=\203?\002O\371\376\330\031\264?{\374\275\033?\255\233\277\332%\221\337w\305\321?h\375\203\265\326\331\270\277%\2132\375\250\315\300\277\263\224\346#\240\000\321\277\'\377\245=\215]\246?}\230\2269\366O\275?\223\264&\367O\311\276?H\377\327/\376\275\242\277\'\th\364I{^?j\024\352B\272\303\301?\256\t2\325\306\276\251\277\302\n\311\0350M\203?f\363\332\354\331\240\240?\345\017\347{\2067\222\277V\325\313\347\177\302\270?\375\353\316c\366\001\242?#\223\007\202\234J\266\277\233.\351/\276\375\306\277-\232Hp\261Jp\277\r\333\000\201\317^\262?1\244\345phh\271?\364\271\274\252\254v\271?\002\371\356\030\321\332\244\277\204\n!Z\346\226\257\277\274\331\273\240\246\017\275\277\375\351\303\030K<\302\277\223\267!\263Q\364s\277\363 \254:\201w\256?\314\354\366\3047\235\300\277E\334\006\350PF\223\277\271\263\204\252\002jw\277\207\301\3557J)\231\277HT\312\270\036\033z?\204\332\037\256\376\374\265\277vY*a]\356\266?\244KV\231t\347\316?9`\324\306\227\"\302\277\266\330\003\240b\347o\277\227/\237\376m\006\262?JX\250\334F\261\252?\225g)\203b\361b?\343\001Y\335\030\333\300\277\316k\3505\223\t\233?\010\034\t\301\261\256\227?*Xy\001\342\341\240\277\316\212\342M5y\321\277\241\007N\212\374\025\211\277\233#\235\371\371\322\255\277\265%i\347\232\233\275?\370*^\224D\360\234?\017\361JL\326u\223?8B#\326\341\300\217?\000,\262\325\363\333\235?4\326#R2\316\301?\224Y\343X\236\036\240?\211I\275$\306I\263?\343d,OM\371\301?$$\021\271\311;F\277ti-\351P\263b?\326\3754\344\021\021\203?e\237\020\363\206\032\250\277\2144Cq4\363\262\277\206\254N\320@\005\270\277B/\372\034=\250\300?\324\247\017\216\345\302\242\277\372\037h\265e\267\253?\301\272j\304\323\020\235?M\333\350\\\217\256a?\364\365d\337n\355\262?\307\357\251\366\236y\221?tN\267uG{\265?\323\373QO\363?\271\277SF\351\220\034T\221?\375>S\365%\334\265?.\270.\374\247^\271?Q\014\326\274\260\215\201\277\351\215s\345x\323\321\277\373\245%\311\n\310\241?NY\265\360!r\267\277\004be\347\2612\264\277\225\376\273\206 d\304?\253\243\362\254\253\346\234\277ZH\270\206.D\261?\220k\330X\336\003\266\277\227\025\361\010e\203\302?\231\302\304\314\354\253\236?k\226\235\021\267\341E?mE\320X\035|\222\277u\\\360\t\031\207\263\277\'\2578\032D\"\274\277\303\277f]\217\374\264?u\003\207x\302\264\257?w\313\2476\025\364\252\277\336\353aKC\323\265?\356\210\014\261P\014\264\277\026\330Q>\253\332\327?P\236A`#\223g\277\317\023\343\250w0r?hK\317N\303M\273?\254\317Pc\\D\237?T\364\251g7y\243?\221\241\210\336\260z\254?\035\221K\004\264\335\243\277\303\366\364\334{\261\304\277^\343\251U\322\317D?s\027\004\205\225\225\307\277%J\207\253\242\352\247?\237f\013\031\227L\245?\234D\034\225D\237\263\277\025\343\242\262\275\236\304?\0060\\\243\335\342\225?\252\364\261\317]\017\235\277\002K\\)\300\216\240\277*\202\206{|\232\267?_\014q\365I\352\250\277`\300\034\262$\362\320\277\251\200J\204\210\014\267\277-\244\211\263\010\337\264\277T&O%\213F\300?\301\200\266\"!\302\316?s\266`\220(\030S?\213l\340H\016c\233?\004e\032k\026z\311?6\272\302\326\217!\271?\317i_T3@\231\277Oz-\270P3\220\277)\301\236c\030~\263\277\3575\306\304\302\337\221\277\3039s\020\034\213\240?\3570\024\332\370L\300\277v\023\036\247};\224\277\243_\006\332\345\263\302?\023\017uq\217\351u?M\205\216F\312\255\312\277b\332\217\255#5\245\277\366\372\307 \276\021\222?,\317\313#\022X\275?\254\232\266\017\221t\274\277\310\236\330a\300\013\255\277\035\\\351\353l\212\264?\355\231\322\224sr\226?j;\257\220\330d\225\277_.i\355f`\304\277\332@\342\020\312\331\321?l#\366\020\307K\260\277\221\204\035d\352\355\257\2775\216\270\307\222\345\262\277\354\322+\337tx\264\277\017\"\325\267.LT\277I\347rT\'b\272?\3754\007\244\326\306\243?\0045\337\213\272\311\243?\365\007\262\\&X\221\277\344\335\363\270[_\263?71\371B\214]\306\277\022\216\252\2013E\264\2777aD6.\230\263?\302\035\340\371\222\264\233\277r4\362\330\232g\213\2771\275o\231\315c\242?\301\032\266\033\335\325\265?r\3166$Y\034\303?\230\021\347\310\237t\265\277\247\335,A\275\316\300\277\007!E\022\253\244\212\277\013\340\001\256\340\231\302\277_\374\370_:{\220?\377\001\217\214yA\264\277\326\'|\304\367\332\270?\025\305.\017\227Q\205\277\323/F\374\271+>\277\351#L`\2628\305\277\231\024\276\272\307\003\241?[U\330\235\317\337\242\277\260\\\324\204\007\222\275\277\271~f\334\377\327\322?\356\321\341\222\273\231\262\2774\023K\315\317\302\264\277e\214Va\035y\270\277\235\350\014\204\021\023\300?W\206\353\225\"\361\256\277\243D+\307E\317\260?\362\370\233i\271\341\271\277\271n\204\336\036\263\240\277\230\364\342\274\335\242\265\277\336\355(\224\376\230\300\277\263\'\267N}\024\242\277\320\310\317[\324x\300?\257\035c\202\231\200\307?\255xX8\227\224\306\277\334\304A\215\200M\264?H?V\241\324\0135\277\004\237\375j\335\316\255?\037t\344\035\2345\275?\310\0047\355\210\r\263?2\220\360\312\222\001\273\277\240\347\301\330\343\266\307\277\035m\025\371\366\357p?\316\351y:X\324\256?[PLn\343P\213?\020\370O\350\314\267\230?\265\2513\023\220L\254\277\366go\245\031(\305\277\255\t\360\330\241\\\320?x\266\210\257\013M\231\277\006\200B\302\207$\267?M\032\247\237M\322y?E>\306\215\"/\275?[\365e}\3525_\277$\271\2310!\216[\277\306C\267\004\247\362\246?!U\200\323{\356\253?\006f.\275k5\255?\037\224~E\267{\245?n7\007\202\266\201\272\277\374\254\237vx\271\270?\233L\200s\375a\274?\031\226|D\302\235\320\277_7\353\033\276\341\204?MN\266\230[\267\302?\203\t\251e\213\342\254?\031\330>\205\362\177\264?.S\220~\324\023\312\277$\326\353B>\335\233\277\241\202d2\326\314\301?\314T\370\037\002\013\260\277&a\342\270\312\n\253?\270J\270\317Qx\275\277? e\210+\030\200?\364b1\373y\365\250\2772b\021\021\256\364\244?\334t\177\21702\261?\312\272\361}\ry\243?\371\325NG\330\357\237\277B\214\370\322\3109\245\277\201\246m9\200\305\263\277\021\3541\235w\367\271\277\200\017c;y\237l?\216\320\232p\370\010\322?<f\336\377)D\253?\242\373r\327!\233\245?\356\215D0\301!\325\2779\025\010#\336\024\252?8\027\021\221LO\261\277\225\340F2_\346\301?\315\250\213I-\342\303\277\342\233\342\257B\374\233?tpl\200\0372\202\277\272s\334\367\027\212\311\277\320\364{\030Y\357\303?N2\352aP*\261?\332\264\371W\216\010\225?\360o\261\275\251\366\220?\273?Z\032\005\301\245\277\017qV\034\2606\231\277\247\346\'\030\256L\234?\343\322\376K\277\217\236?\177\362\233A+\373\250\277\366\333^q\336\330\235?\355\030\303\013\354$\325?\215\301Q\221\321\361\256?\244\254\323(\372k\301\277\370\273\rw\r\345\205?R?\325\274X\306\300\2771\253\023\203;\255\261\277\257\032\251\335\207F\272\277l\251\320o^@\267?V\221pc\212|\255?\366\352F\264JU\267\277_Y\035>\213\010\274?\332\227\201\252\246\343o?\377eN\217!\320\240\277\356\344\370\326\226W\205?\215J\237\361\237\244\303\2776\203\245\024J\312\204?\353q\371\214\273V\265\277I\0228\356qRe?N\330u%S\t\266?\362\335\213\014$\362\242\277a\267l\277J\376\266?\036\206\344\225\306Y\244?j}\227\220o\363\232?d\363\214\2109h\261\277\366\346\221\356\241\371\321\277d\342\330D\357Q\252?\273V\304\035A\204\252?O\255\344\303+\032\267\277\322\316\350\334\226V\307\277\001\254\026\'\024\367\232?kH\263\243\305\202\261\277\331r\367%Cn\257\277\377R-\325\0059\301?X\021\037\242\022.\253\277\240\235\351\271\304\357\213?+\020\345\354mK\300\277\337\024\266\000\312\n\277\277\246\214\032\356\003\202`\277\360\371\030\234>Yr?3\342\336|O\325\214?\272&Qg\332\302\264?\233\177S}\254\200\244?\364\350\014\375\035\235\222?\036\002\307\3662\374\263??\207\013<kW\243\2775+\256\202\032\034\267\277\200 \205%s\371\261\277\244\366\266\005\235O\243?xg\203R\372\343w\277|NI\026\020\345\303\277\271Z>2_\240\245\277J\304%\234\353\301\242?\275\355\265\035\324\027\310\277\305\301\330C\332\351\247\277\276\245\244<\265\210\300?\035i\230\264z5\203?\301\322\300j\226\262\257?\336wN\211\212\327\207?\331\232\242\306\030;\220?\r\264,\270\036\243\223\277\030\313\255\277\245\374\220\277=\201z8%\373\245\277\014\270b\210\020`\301?\237\265$\302\267\263\274\277?\331\233\274B\005\266\277\037\003\027\262\010\324|\277\223\352.\327\014\006\316?Z\"#Xq\241\265\277\304\006\205b\224\345\225?\261\215\212}\353\330\253?! \301\016H\027\303\277\035aA\214}K\274\277p>\242\002\335n\302\277\035\356\257\346@)\261\277\365\204\020\260\204\\\273\277\210W;\332\233\270\247?\347f\211\342t+\307?\tD5T+D\300\277\264o\213\023\303Q\265\277\237\330|\025<\"\206?\343\221\347\346\037r\301\277\317\366\035\270l\265\206?jm\267\265o\217\251\2770\243\263k\006{\260\277\027\020\374\251\324~\321?\241\355\'\356Lx\220\277CG\020\360\321s\275\277\016\246\316\007\314\244\277\277\212\264\014y0c\311\277\016\247\201\'\363\273\274?\230D~^\006S\237\277m\205\207JPgu?\321\255\255\200Y\031\272\277\353\373\026\000f\026\277\277-\255/\317\211%\321?\343\300\351\003\331X\242\277\237%\272\312[\352\266?M\226/\304\354\3054?\t\325\211i\366\037\262\277\360\024o\362\260y\243\277\021\333\0104\372N\207?\367\251\335\025]q\265\277\361l\362\324<4\226?\20448\222\330\260\237\277\020\"H[F\210\263?e3\203\263\357\026\273\277\217\301\261owO\254\277W#\244\251\232s\242\277\177\312}\024J]\307\277M\250\201\230 \014\200?oC\032\002\257 \276?Z\344Rb]\350\242\277\272K\017\374Y\367x?ii\224\326\263[\251\277\030\362\237\364\254\t\327\277\2005?T\355\250\262?\221\354\250\2564p\220\277G\001\220U]\366\250\277J\014-CR\351\236\277\313\314:jJ\010\300\277\"\243\032\321\206.\250\277\373\220\301\034\271\376\302?\023\365#I\340\213\307\277~\231%\312\356~\211\277\267\214I\224d\014\276?\326\031\274[\226\330\266\277\350n\245P\255\004\224?\357\2434\227\263Q\227\277\347l\235q^\031\254?\tr8>\260\222\274?\326\320z\203\010(\254?\347\034\304\374\277\016\243?\374-\370k\217`\276?nW}T\2775\307\277H\312r\343\276/\307\277\361\336\356M\253n\243?\211\177\252\350\nkg?s\377\266yhw\251?\345p\307\207\240\251s?\303\200\303tR\235\266?\257\366\322\364/\200\300\277\353\323:am\201\220?G3\222\220\275\236\242\277wkR\323\262\302\234\277\221TG\350\335;\250?\254\236\363P\301\036\245\277\303\341\370d\322\225\305\277\354\374\364\301b\036\306?\375h:\370\031\035\256\277\342\310O_\374\352\262\277\376\026!v\341\014\307?=\027\207<.\261\204?\031y\303\241\345\204\302?m\306\233\361N\023\230\277\"\320b\245[\017\307\277j\271\016t\335\302\247?\320i\214\370\310\270\300?\336Y6\007\302\226\253?>\240$a\340\231\224\277<\313\207b{P\262\277}\356\264\333\221\374\253\277\307\033:\223\363\250\316\277/\377\335%\237o\236\277\027V\360?\324\247\271\277m\257\3548w\036\235?\353\020\000\210\272\007b?\375\374A\214(\230\261\277\201\307&\376W\350\320?@_\321a\374,\323?#2$%?4\227\277&J\331\302\020I\272\277\344\010\312\370\326*\320?\203\327\230\366B\344\242\277,\331\335\313){\251?\332\300vS\221*\227\277~\263\367\3672J\271\277\231\311\265\211\300\004\265\2772\335]\324\213t\235\277\255\272\255oc|\237?\231r\2729\003/\245?\355\210\365 !0\301?\010a\372\\bN\250\277\000\265[\356\272\312\257\277r\253\225\t\336\312\270?\352>\231\\\335M\252\277\010\302\010v8\027\311?\312N|u\025i\240\277\261|U-j\371\226\277Ul\355\245 \371\271\277jl\372%m\362\277?\246\323\202Ct\220\257\277\377A\215L,\246\310\277B\314L(\317\346\304\277\202#\026\0246\361\257\277IZ<NG\316\272?S\"\312Y?\254\272?t:?\245\'\376\266?Q\036@\265\335\374\265?tXJ\331z\034\257\277/#\340\257\267JT?\345\263ulP.\243\277\"\271\3178S\346\266?VJ\n\211\340\333\300\277\370\'S\306\317Q\254?;$\267\270=F\241?\363l\317z\375#\245\277pn\362\367BV\274\277v\357_H\021\037\216?\270\020J\024N\035\217?\323\003\020\332\204\272\263?\373\320v\252l[\305\2772A\277\301C\344\252\277:\206\273Ta\036\257?\267\036RT\003\022\253\277\014!\207\203=\305\206?I\267\336L\241-\221?\322\363pN=\320\226\277\230<\017%\225\274\261\277\341+\024{P$\270?\316\221kR\266\266`?&`\335\t\337\266\267?4\267\001fD\317\222\277r\366\266D\236J\302\277\t\352dO\224\023\262?\217\226W\203\327\273\304\277v9\200\243=za\277\"\376\364[\t\203\226\277\256PQ\007\004\262\314?\365\205\231\327\313\014\307?$\017\210\332.\311\270?]\303\247\'+<\275?\335\324G\224m\n\275?)\251H\300\026\002\242?\004?C\213\277p\217\277\373\027$\376DY\266\277\314\001\244X&\361\256\2775\260[\334\232\313\242?\260\2527\263\035\261\256\277e\3576\231\302g\216\2777\023\014\350{J\236?ti\334\002\2238\242?4\034\301\215\177\207\243\277\020\n\363m/\207\265?P\325E\364\354d\237\277\245\315\356=[B\237\277L\321\301]&\343\266?\276\372\"\004\267\217\267?\262\335\"\006I\201\276\277\250\250\207\315\3565\226?\034\311T8S2\237?T\266c\375\202[\261?T`\017\215\275T\276\277)\205:\337\204\363\213\277\352\207\317\350\025;\207?-l\327\272\372%\311?\335\341\010/\336\005\216\277j\307\253\373\342\223\207\277\247\251R\345\020\263\211\277\245\364\360\313\214\264\233?!Z\331]\216\242\310\277\304\026\353B\216\354\225\277\326\013\323\274^D\257?\232\204\266\314\230\312\247\277<\320\265}x>\263?\340J\223\375\215}\316?\023\220\343\361\337\375\302\277\216\001\315\'\322\034\220?\210\345\"\223\330\202\263\277\376\244\366z\202\321\266\277\352\322\000YT\230\214\277\316<\267\246?Z\262\277\312\017f\024\330q\276\277\236sVHU\377\264?\200!\321\361\016\004\221?\303e,\364a\016\242\277\177\363o\243\024\253\253\277\206e\267\031\357\023\276\277\252 ^\273\004\231\203?\016~`\335`\021G\277\264\302\265F5z\272?e\031\230\343M\233\200\277\022\2457\205\226\014\211\277\222\311\026\307)\334\271\277X\362\330\001\205s\302\277U\373\3237\270\364\255\277\323\245\010R\354\346\241\277\306\277V\310\240h\243?\260\335&\235?\363\252?\242\367)\336D\327\240?\333\022}\224\205\033\260\277\272\236\342\245\235\302\256?]\345\\\362\341I\265?\210\345\t\371\367\000\234\277\026B\221\374\216.\241\277nB+\311\255`\267\277\035\023\014])O\232?\310~\327\002:\304\211\277\355\341\314s\232\007\245?\"\221\302\314\375\246\302\277\276\313\253\340\215\005\203\277\321\334o\270%\264\304?\272P\"f\354\313\303?`\nc\243@&\276\277\'U8#\014\220{\277d\361\321j\363}\270\277\035i\210h\201=\251\277l\004D\275{!\260?,\244\331\305\036v\266\277\344WSKF\334\311?\345\001\236\365\177r\275\277\223m\303\213\016\343\233\277\306\276v\271\023\010\313\2772\354%\315\201\343\225\277\246.\240\352\217\323\245\277\200\222\326\246$K\303\277\227\337\223:\252k\332?\266\002P\273\232\016\220\277G\324\256\304E\303\272\277\242\004\351\235,:\227?\317J\277\335M\306\270?_r\272!\033\334\304\277\222\231A>\023\025\310?\304\344\024\002\211\326\273\277\235\266\252\245w\271\241\277\202\025Xu\321\214\213?!.$\243T\257\313\277\035\223\361\325L\275\262?^\331W\277\030C\242?\035\236\263;\230\252\306\277\267\205G2\036\330\314?\031\343\352\240m\320\215?\225 \242`f0\277\2778*\220\335<o\320?*b\033\026\227\223\272?\004^-Z\\>\321\277\2404\327\242\321\321\257?\000\331\0149\243\261\321?\257\007#\241/g\234\277\36634\276\320Sr\277\233\241\273\334s\357\267?\316M;<\302\203\272\277I;\332K\346\010\237?\245\372\377e\265\314\266?7AT\335\n;\222\277\177T/\275)(\220?\301\257\317_c\342\247?\3603\006\307Z\215\204\277\333\345\177\225.\\\242\277\002\324\177,\"\024\257\277\004.~RV\303\276?\006G,\315\306\002\225?\177\334Mr\276g\240?\316\365Q\335\262\024\234\277\314\270\217\027 \364\256?w\357La\265v\257\277\300\223\376\236{\256\252?#\313\022\204\350\362\237\277\361}4\037]V\237?\362E\320\235H\217v?X\304\302\2535\305\265\277yt\014\337\355\036\232?\016\372wE\365\304\242?\177l\0255\007S\247\277\023?\333\037\220\234\263?|\002\242\243{^\315\277\345\334\337\246y\223\244\277\t\335(\025u\324\274?Vs-\377/\301\310?Dv\325!C\306\273?j5EP\014c\273?HB]\244\\rC?\267\245\211\256\332\241\313\277\372\276d\247\275s\243?\273\211\"[&\373\230?C tt)\334\254?t\020Y\330tw\243\277(\263\236\356\2546\242\277\334E\366\307\nS\240\277IPW\036\375W\330?\315\362\312\251H\336\227?Kj\201\371@M\266\277?\0070B-T\305?\000\232\236\356\273\355\315\277\002JvY\300\321\274\277a^L\361\261\307\272?+\240?~\205\370\267?\302D\317\210Z%\262?\224\320\372N\2475\300\277D\3611\323\273\301\253\277\267V\014\025hK\262\277eu\347\206nC\301?d\\\236K\333\344\303?\347\345R\326\251c\240\277\214\237\023\035>\232\220\277Y\331\224\032\224\330\227\277\203\311\305\036Q\177\230\277\326l\036\206\304!\315\277\333\211\301.\351M\262\277@2E\206\n\024\265?\261\375\300,M\224\273\277\345\353\2510\324/\207\277\200\020\274|fx\255?\231\262\035\274@(\302?\002u\201\360wQ\317\277\017\313\2541\345\225\224?{\242M\254\346\275\235?\2362\240\336\254c\254\2776&\033\240\243{\243\277\362k\017\300\3347\310\277\200P\253\214\006X\261\277\010\305\352A\211\261\311\2770T\243\350\270\207\220?\021R\320\302#y\211?\322\254\335@\312\323\302\277\315@o*\367\373\276?\232\375\\\265L@\266?\0066\314\235\273\363a\277\016\002\rN\"\306\265\277\271G}\231\270\344\253\277\347\354\005\\\307*\245?\024\332\331\235l\246\310?\240k\204\252\327A\241\277_q\303\007\307\214\246?\314\275\226\025\342E\242?\021\331\327\205\304:\223?\022Sv\001b\227\274\277\215I\200\267\267\253\222?\250\272\344\231\333\256\204?cDp4\331\027\240?IM\272\n8\343z\277E\252w\336\022H\245?\222\331\210\302k\023\263?\361\220\255K*\200\266\277p*\3310\347\257\254\277q\001\203\177y\353s?6\323\022XF\255\222?`\211\361\365\344\215\232\277\247\222\301%6\213\272?\2246x\004\210*\232?\004:_\237\376\263q\277`\343ae\242\024\273\277\355\313o\002M\000\213\277\214\254/Y./\254\2777\250\346\266\314\014\224?\234)S^\365\367\311\277\274\325I\212\271sF\277\361\317\2022!\201\305?1\3753\002\\\276\252\277\314\204@\224*a\201\277\254L\030\373Nu\244?\236s[z\234t\263\277\224\212\274J\321\241w?\226\245|\316\300\025\304\277\234\213\325\177e\036\217?\362\236~\014\362\301\314\277\301\300\361\335\304\227\221?\365:\033v\250\207\276?\r\351Q]>\245\250\277\007\037\242\253\326-\320?\355\350\376\372\350\364\252?q\306\373V\377\335\255\277<ny\233]\325\270?_\356df\243\261\264\277\'\265xT\342\242\247\277(\250\372\372\334\335\237\277+\213\247\204\233\255\276\277\263 \002z\304\274\274\2777k0\351\233\307\213\277\001Y\360\317*i\244?k92CV\264\257\277\302\201\014\224\265\004\274\277,\350<\362\252+\244?\036\024I\252\251\017\253?$\350\007\250\032\023\220?5|\244\213\206\007}?$\217\347\235\245\037\307?a\212\212\"\311z\322\277h\343\272R\303\312s?\3474bL\002\320\264\277\026\016\266\335\202\340\231?x\236|\215\022/\321?\365Y\322\356Q\266\264?L\nH\224\351\352\262?\307\227bD\350G\260\277\227r\300\241\263>\225?\241\230\254\2475\365\221?1!\014\323\275\n\301\277\2067\233\325\246\314\251?\263\225\357\360`Z\305?\004\356\n\350\003L\273\277{\215\033\301\372?\261\277%\200\323\360\355:\264\277;\246EU\355W\262\277\240Z\267d\026%\322\277\353\275E\371\362\232\262\277\377\264\351,\\^\262?\0359\214\325*\224\240\277H\343zJ\225\265n\277\307\034\263\3367\224\245\277\231\310eH\245\201z?\3349\371\325\271\367\264\277\314B\020\010\2000\242?o\246\344\014\000\204\243?\254\2604\227\024\005\223\277\200\213[f\037\260\262\277\371\t}\035\216v\206\277\'\343\276L\304\037\235\277\177\243\233\272~\272\261\277\250>\372\025\273\207\271\277}.\214\361\021\"\255\277\265\322\213F\337&\264?=\307\033\014\271\351\275?\372\343\306\026i>\212?\007\352\320\302\325\232\234\277f\274I[#.\301\277\022\022*UL$\276\277\341\247\257k\256\203\253?\277\007\367\333\311\216\224\277\236\"\002f\313\251\302?\330\236\335\247\"(\303?\037L\222\177\235\233\236?\373\235\221\205\371R\256\277)7\263\335\255\227\261\277\323&\201\365\246\001\260\277$\003=\206R\310\242?\006\232P=\303\277\307?o\302\313\356]\037\305?\311X\374\376\026\205\301\277o\006e\25045\242\277(+X|L\361\244?\3439\024\3653\303\302\277\231)j\031\243\241\264\277\272\330~\277\272\204\303\277\034\250\323\340K\364\244?\367\001`\"\237\006\266?@\034J\232\277\360\267\277J7C\013 \350\247\277v\247\321\244\323\034\240?~\221\002\275\221\227\245?\217.&$/\342\302\277\376w\230F_\014\272?F\340\302\251\207\010\265\277i{`\3677\211\270?I\260JD\320\302\260?\211\361\203\0372\007\314\277\305\\L\246\216`\306?\267\207\203sB\331\313?\027:\\\242\227\326g?C`9\021\311\354\245?6\t\350G\177\026\203\277\"^\016\353\276\271\252?G\3605\264V^\253?u\265iJ \235\271\277\030}j\355\3658\307?6\255x\263Q0\201?\205\000G?D\253\264?F\222\216\247\305F\261\277\261Ua\274\331\243\212?-\336\203\013K\203\225?{\032\035\0136\'\266?\313\334\221L\372^\304?\344z\216\365 Vr\277\305x{\302\033r\230\277I\324\337[\255_\303\277u\316\211*\016^\262?x\230\333y\350o\273\277\345\'m\0240\307\263?y\240\245\223\366\205\177?_\351\035`\025?x?v\315#\226\2335\261?\026upS\212\212\223\277tn\'\177\036\016\275\277\222\2340c\016\350\277?\357\037\361\277]\370\251\277\310\371+\253I\321\322\277\007$\222\354?\360\250\277\256a\373\223\263:\240\277U\217\326\206K\253\226?\2141)\223\nhy?\"\211t\362\253\237\377\276\333CT\245\325\223\237?U3\n\323\366\322\323\277\017\276\276\273y\204\234?\210\244\\\245\213\362\255?\251?\336M[E\270?.1n\243{\030\253\277\270\261Lxq>\241\277\276\263\005\320\372\'\267\277Q\007\246\353\212\367\231?\357\312\3764\376\332\261?\270\374\362u}\255\242?\371\370}\350\233\215\307\277\334@`\226J@\270?\237\201\004We\301\240\277n\370\037\374\366/\312\277\363\313\316_ =\260?+\202\371\342\342R\205\277L\362\347\312\226\311\231?\201\203#$(^\300\277qa\224\364\357\270\257?\033\226\360\274ke\255\277\357\316\207d\363\301\201?\201\2354<\344\234\261\2771\024?\310\223S\311?e\320T\262\022\276\266?\375i\003\334\005\305\201\277|:\261q\361\356\300?\334\277\313\247\340\313\223?\245[@\214Q\030\252\277\360\376\342\300\204&\267?\321>\272\003}\203\270\277\007\353\246,m\267\221\277p^REJ)\303?\320\250O2\2768\234?2\204\013\250\255H\255?^\337=\360\037\246\270?\233\311\370\340\3313\327\277\301\274;\036\321\243\270?K\301R;\330\336p?6\353<\230\252\370\206?S\322]\373\321\235\231?\211\312G\n\300\247\250\277\214\032\035\361\000\236\301\277\021!\367\215\377\217\300?\206\357\244\014\214\201\273\277\331\204\270R\315\302\214?\206\244\013\216\010S\242?aAj\375\337\347\224?\362\266\034~7\270\310?\261uTv\022\r\214?\324\250 \261\010m\265\277W\200\243\245;\345\216?\330P\231\226\0212\270\277\267\021\330D\207\223\312\277\"\235p\035j\003\310\277\370X9_\373\221\271\277\006\2351[<+\247?\344VU\t?\364\265\277+1e\036o\262\232?\317\013\327\001\225;\250\277\023%N\355\337Y\264?\013\372\200q\305\235\275\277\206\347\267\201\217\315\300?;\345\366_\236\002\263\277\357\020[\r\270\222\253\277\357\376\223\302\231[~?O\314\254q\252F\302\277\032|\307\250\326\362\302?\263\367\221\211\007 \251?\377\014T}\362\337\247?\373\266\024\360`\303\263\277I\227?e;\276\250\277,3\006w\r\303\250\277\325/)\262A\017\255\277J\215\353\366T%\273?\272\303\235\260\342O\235\277\371\325]\205_\365\207\277v\324\221\215\004i\223\277\2465aA\374\362\240?t\213\022_$\214\221?\373~\202\244J\003\223?\322\230\243\030%\034\306\277rB!I\367\367\273\277WjarU{\266?\220\314\316yb\222r\277L\225^+\241\021\222?\027\033\346\000(\226\227\277\361\336?\265\233\025\255?:R\233c\326\005\226\277\005 )\252\277\005~\277\266-\310\243:\017\224?\265k\210\323Sp\263?\016S\025\266\236\232\311?p\300\265O\027\244\254\277\254\272z\332q\311\251\277\206\363\026\303\365\312\266\277\032\002&,f\016\267\277:\253P\333M\'\240?K]\267p\375\245\311\277\237\236\337\227>F\250\277\201QK9\232\317\206\277\001\025\0200x\207\266\277\275~H\345\327)\250\277\241\306k]\031\322u\277\016\301\327\036\303{\233\277\230\306KAot\260\277\3328\333Bt[\266?Q\274\202\034\360\307\265?_#-\366)>\222\277\330\227,1\341\306\262?\014\"\367\t\014\334\207\277\243\307\355Up\246\236?\263)\377Sj\240\266?\032\370\236`@Dv\277\214\177\235\321\241\211~?1\303\325\344\021]^\277\003G\032\221\303A\305?\326\367A\026\024\326\243\2777GO\214\312\370\215?/2\255\2409\000\220\277\205YDBZ-\271\277\253\3636\270\271P\260\277xe>\nP\320l\277\313\240A\315\240\341\260?(\342\020\254OE\305\277\253\336\327\021\350<\273\277\r\257\375\366\371_\231\2772\223\376\010\017nt?\237\342\352+>\014\273?\024\236u\376\255\017\225?\307R;\005@\214\301\277\367*\230;\n\342u\277\200\343\243Z\364\242\224?\372\376\234\236/C\243?\003~H(ap\265\277\313g\270U\020\303\205?e2\207\347Wh\252?\315\361\2675\202\330\300?\372\315\220+\0234s?\226p;\306\262\t\262?|*\250\263\365\254\274?\377\220\274\327g\263\274\277\'\237\334w\365{\264\277\213\317\357v\314\213\260\277\216\340i\317\0135\214?\255\330R\245\230\346\244?\240\02262b\007\201\2779#C\370b\353\271?\2724\213\366\242H\245?\270/\373\203\032\032\233\277~Xg\'\306_\270\277\\6\244\204\373b\205?\253)\3539\356u\256\277r8\232\305\361\257\242\277G\272\021\0168\263\224\277x|\010\244\340k\210\277\234\274\325\212r\002\253\277\353\007\254\247\030uj?(gC\247\332\203\315\277\007\272n\0364i\227?:k\2236\201\334\273\277\267\363\026+0^\313?\003\350\375\363\3539n\277X\020\200\320\037u\272?\270`\344\303U8\262\277#\374y\243\333\340\223?7>\301\014\023\027\306?\025\265}9}\347\267?\227\337{\314\372\002\302\277\277~\313U\252&\256?\250U>\332\336\010\243?\331\376\376Ck\237\211?\335\037\374\027\333\352\243\277?\030(\335\313;}\277\023\256\003q\312\364\270\277\016\351\326L\234b\254?\361lOP\307\213\303\277\201\244{ \037\207\262?\221Lp\255h\372\265?\274\242\235N_\276\257\277\303~-\"`j\222?S\275\263Y\215\311\300?\377[)o\352\002\251?4\207\351\313\3251\213?\n\311\215\232ip\273?\372y2\375\310\354&\277^CZ\304\352\351\307?\024\322N\022\343\334\265\277\024\337\002\347\364\346\261\277\n\315\222\033y.\264\277\274YZ4\371\302\250?R\177FX\271\270\250\277\317\336\324\304\2010\277?,U\373\211\177\204\264\277\r\335\340;\303\343\235\277Bg\003-)\032\276?iD\301u:-\302\277\315\207\355\3039*\303?^\016#\307\374\375\265?G\036\216\357jA\242\277<\347BV\021\331\251?\361:T\3759M\024\277\273=\266\252\356\302\312\277q\223TE\'\310\324?\231^\230\271J\364\240?r\354\007#\301\027\263?\031gS\025\276\206\317?\021I\226\201\001w\267\277\0278G\3716v\300?\361\313\310\337@\272\236\277\3504\271\2463?\273\277\026\023\242:\272\230\263?\201\326`hW]\222?\311|\t]\215\321\261\277\rwVeQ\375\306?Tb\247\322tq\250\277E=lz\262\253\267\277\351\033(\213\310+\267?vE\226+x\237\222?\r\336Y\320\321\261\235\277+\007\335v!\213\214\277\350\027\013\nm\271\277\277\210\327y\215\371\361\265?r\013*Po\335\202?\030>b\2325\360\235?\r\205y\t\3366\260?m\037V\201\374f\272?\0258\323\010\0019\310?K\365EF\352\220\277?0FJ\016\306\260\231\277\217_v\307}(\236\277QF\225\334\237\226\324\277\361\341is@\230\246?}HO\277\367,\230\277\030\352\327c\212\342\273\277\272!\266.\303\t\260?\322\264\252=\215\004\233?\n\033\223U\364\203\273\277\302\027\332\010\374\223\272\277\257F|\301k\213\270\277&\242dZ\363x\252\277q\023\355x\354o\272\277\363H\261\312#\035\270?E!,,\300\377w?\343&\222\262Ap\311?\235\'n\300\233#\252?\335\360x\375\313\224\306\277@\347\234P\222\237u\277\205\".\246-i\213\277\260\016\265\006\374\205\262?\200?z\037r\320~\277\t\035\026\265\253\356\246\277\216\224\371\272\235k\277?\350\365v\270\025J\311?\254\207\3522\364Lr\277z\030\320{\236\n\222\277Z\005)\022>\327\245\277\204\354\362\214\375r\215?\352\253\374\274z&\252\277\304\037l\245\006\223x?J\227\235\221\340\377H?\265\200\252m\033M\201?A\236\262\371\332\311\301\277\232QE\347\351\341\302?\"\253\003\265\364T\245\277I5S\350\237\264v?\002\n\345F\327\355l?\262\275\211\000\0200\217\277\273InZ\361\334\270?\220R\"\376\361/\263\277\347\200,g\307\322\316?c\001\000\327\265\252\241\277\360\374\361X!\037\206?\250S\352i\261\237\266\277\270\305\3261\376\211\265\277\300\237U\325H\266\267\277+\303\232\200\3129\311\277\001\363\354\272e\372\302?\003\177\202p\326\267\301?\277\256#\252}9\233?&\361;\026\251\334\243?/\177FgCk\220\277\327\210\001n%\340N\277\215\217\001\302\235O\255\2778$\032^\217<\262?2+\202\n\0216\221?4\200\271I<\\\252?8-S\221\241\022\233?\007\373\212]\211f\263\277\034\004\3500\307\202\231\277\326\\>js\315\257\277\205\363\363X\304\315\301\277\325\353\254\373\202\360\253\277\025\010\313l\300\232\270?\277\243\030\226:^\265?\0074\357\247\211\016\301\277 \273\364\252\242?\263\277\204~\\4= \242\277\224#\3103\341G\263?\3117~\221>K\241?\363\323d[D\253\267\277\275\330!\031\2721\200\277d\234\272\267\274\345\271?5:\003\227z\243X?)\241\327*\"\245\247?\246\272\236\023*\207\241\277N\004\325ZQ\016\255\277\234\325\363\3676\332\260\277O\177|\014\030m\257?<\201\261\030\227E\272\277S\360!\007C\374\244?\377\306{\327k\266\223\277\r\255\027\236\316\025\320?\376\230 \347\024_\276\277\207C4\351P#\225\277\001\360U\232\232 \277\277\337-\276\017\257\221\261\277\211\026\245g\361m\266?\255~&\310\356H\257?-\024\230Z\004\004\267\2779*o\010\224J\231?\0179\"zD\323\226\277\261$\212\300\356\004\231?\246\022\257f\010\336\250\277\202\001\300\037\363\262\260\277q\312\014\232\341\'\275?6sKT3\000\251\277d\320B\331\005\013\255\277\027%r.<\374\201?q\365\343C\262=m?]f\204\034\020\360\261\277s\222\335\037\251\376\264\277bz\237\265\222\010\227?\r\3144\026\224\305\300?-\244BDv\206G\277ia\251w\254y\237\277w?\374\n4}\300?\325\226\371p\023>\307\277<\262I4q\217\270\277\226\"\'\031\377X\260?_a\246\341n\357\202?\226\314\243\265\276Q\234\277\231\n\240\364\\\206\310?\n%\034j7\324\305\2774\020\276\020\306\'\313\277\302(\\\304A@\244?A\334\002\351\207j\251?\235\337\315\033\223v\204?\276%\021\355\355s\261\277U7A\026\265.\251\277\303\022R\207\325\271\256?z\036\001M)B\320\277H\266N\223T]\227?\253\324x6\312\200\266?\035;\343.\321\022\303?v\366^\206\356S\261?\326\001\210%z\354\313\277\251X|\330\262o\245?\254\355{\035\200\360\321?\336B!\362C\250\242\277lc\265wO\244\215?\036\243\357\204\037q\221?A\tr\025T\235\204?\345\314v\026\260\204\265\277\'\226J\344+M\301\2779\361\243!\351\350\317\2772\240\032\207l\371\237?f\225\373T+\220\246\277\324\371\n\036\2401\234\277I\201\237\220\207[z\277\300\222\354\252\212.\202\277\322=\300\361Kr\263\277\"dM\310@\307\312?\031Q\350\236\025pb\277\n\244\356U\260V\220?\315m\260\361h\257\311?\002\354\350\375\277\330\244?\352,z\367\000T\266\277\004,+\345\340\251\301?/\014_\010\325\306\304\277\236H,\023\232(\264\277\227L G\271\200\251\277\352\037}\021D\320\250\277\373;!U\351\304\270\277\241\246\367\336\203N\240?\3478#\022\036\342\252?@j4\341MN\255?\005\207\032\324\307\233\264?\336\342\235\271\242W\276\277\355\000\315\364D\231\221\277E\005c\367\2514\261?H\223\215\311\366\262\247?\213\261\246T\010\337\256?)\023>F\271\256\243?ON\025\220\004E\256?\245\307\365h]\362\241?\313}A\307\021 \313?U\017\277\216\274:\250?]\014\277\357\271\275p\277\221l\376\253\343A\257?\276\003\351\240.\020\275\277\020z\265\267px\226?\272O&w92\261?m\230\307@\321\220\251?=\310y\201,5\304?A\244\272\375\245\351\263\277J[\237\311o\366\242\277\260\023\377\212\325\203\235?\226\016\207\336D\363Q?y8\207v\212Y\263?\271\234\211)\020&v?\245;:\271\353t0\277\363N\314\251c\010\273\277f~\311(\254\214\273?`\355\217\263\271\023\262?\323\241\201\211vX\244?xC\240\246\225\301x?\320\315\263@4\036\242?\253\262\240\275\242N\257?\203\325\372>o\320\306?\274\323\277\332\273\017\302\277\262\206\321\326\310U\320?\257\\u\037\261`\246\277q\241\230\366\207\250\260?K\341\300\273\221R\244\277 \035\016\224\207G\220?O\032R+m\302\251?\rF\003\224\241\244\262\277>(X\255\n\005\315\277\2652\216\207\304:\240\277+[\211\200a\207\224?\211\277\337\214@V\301\277\332\357\032\333\205&\240?\360\3038RB\375\261?\002\007\260\321\376W\245?\203\022D\366\032O\332\277\320\246M\264\347\372\253?O)\273\374\246C\247?\320\013\263\372}f\323?\320}\204\321 \311\276\277v\262:*\246\335\264?\205U\304JX\327\302\277\333\251>\334\203\321\275?\227x\037X\027o\277\277M\237+\257\026O\243?\032\355\'\2414\244\242?\207\327\023\\\355\312\247?\001l8J\273;\265\277\014\300)\032\005W\271?\025\275=\340i\363\241?\273\200\315\321\202\274~?C\276)q\274\347\253?;\027\231\247L\261\301?\355>W\355\211\020\257?F\300\'\tw\033\302?\341\004\261\224\252\223\301\277U\003k.\031G\320\277\321F\211\224G\353\235\277\353\375\252\373\315\210\214?\206\321`DTi\226\277\023\335\261N\375\266\300\277%\335\271^\245\004\231\277\305Q\333\232\232\244\266\277`\004%\207~\177\267?%\001\325z\202o\241\277\317},\203\274\244\250\277\362\336s\313z7\276?!\027\274g\323\212\246\277\360\216kg/\313\212\277\361`/\342\305@\266\277Av\0132\232\305\263\277,\301*pu\213\277?\005~\355\234\032\272\232?Y\267J$&\215\300?\237Q\010\026Z\220\261\277\240\335\001\310x\271\253\277\r\370\315\016a\363\272\277HK,\365\275\331\267?\273\000\307\036\314\020j?\203\016\315\376L\265\261\277\331\200\373\305[X\266\277M\237E\313\256*\232\277\376\234\313\244\354\253w?V\024\363b\325\021\303\277\023\rE\321\025\\\300?<\200r\353\316\035\244\277\315\200|\335\267\265\202?\264-Z\n\210]\223\277B\"\3700\205\277\220?\253\352\263\241\231(\245?\344\230\272!\017\031\261?m\200\367\032\362R\240?Y\3663\235\026\253\305?\241\370J\032\234r\252?M\177\253;F\246\275\277\003\265,\323q\222\245?\362\357\356un\260\304\277N;\017\005\240\255\262\277\300_\365x<(\304?\223\203\341\3018\327\214\277\316\275 (97\256?\317\301\202\266\271\014\261\277\356\374\367q\246\374\255?N\376Od\020K\233\277\270B\207\272K\010\230?h\\ms4D\307?\024z\014\253\3526\263\277\240E\265\235p\240\202\277b(\227\377\3252\317?\2301Q\242\221\261\254?\231E7\260\352\006\261\27730M\266\2546\312\277!u\242\005b\252\265\277\305\016&\337\220\237\244?\ntI\31472\246?\323G\212\243\031\213\243?\213\002\367\305\204\\\277\277[\001\204k\256\302\203?+\345\277\357\235\326s?\370w\014\334\276\233\320?\204F\217)\315\007\236\277\020\267\177:\265\353\264\277A\212\203@@\000\231\277\300Au{\030\215\264?{b\340\007\315h\202?\366/\323]b\302\310?\342L\3007\272\363\324\277f\226\233d[}\226?z\225Zo./\301?\361\250\245:\010v\310\277\312\373\014q\302\360\273?\315\277\324F\006\r\230?\245\207\'\227\215^\261?\216\254\366qB\310\254\277\340jGu\353\275\255\2779\232^\212}\371\262\277\341\224%k\371*\277\277R\212\rX:\310\232?V\325\235E\341p\246\277\311\027\234\332\022\247\272?\232\'\351\364\373\334\311?}t\217@\360\032\220?\263\256\2058\213\000\263\277\303\242\006\2632\351\203\277\365\244Bi\367f\277?\325\246.\303\366\356\272?L.\272^x\356\202\277\312\212\330-\005\362\242?\232\357\354\240\014?\201?\364\001*_\025\303\264?\007\215VI\226\273\272\277\t\357\231%\251\331\220?\242\253c\226\222\336\304\277\375\010=\030J\031\211?\365\206c\n\343%\254\277\323\303Q!\254/\245\277BWtr\366\325\275?Fk\272\345\307\272\247?\3173\357\031\013\336\267?-l?&%\033\262?\0328#H\350\267\260?i\024G\265N\276\271?3Z\251\235\307o\304\277\323\271\263\357h]\241\277J\230\237@e]\262\277\017=\006\323\365\273\256?\010\257J\320\262\177\303?`\267\3252v\304\271\277\2355\350s\245r\306\277n\251\260\237\305\306\261\277\211L\341\250\352\351\257\277:\241\366;\0366\317?\0208\224\305\326C\276\277\2659\360\373*\377\244\277\276\360]\005\275\303\215?\314\262\025\017\376\243\272\277?\211\"^\201\231\304\277\002O\351\365]_\243?_\032A!\357R\313?5\ny\035\177E\275\277\326\262H\321\0357\300?v0\236\223\177t\301?\3743n.r\261\322?\0031\236\311u;\246\277z\245 Q2\277\311\277\3208c\331\335\235\275\277Gt^b\014c\277\277\327v\020\177\274q\300\277%\2758\371M+\315\277\202\257|\005\254\006\317?\343T\260=\002\333\234?\256\272\326\3674G\235\277\023\340\351\267\233\234\304?\357\377);\007B\251\277v\257\243\037o\240\260\2772Md[\232p\260?\245\336v\255\306\347\322\277S\246\035\"c\347z?$M)\263\215\357\300?$7\2542\210\032\317\277`\306s\273\027\335\276?ut\221\323@n\220\277\201\263\0029&h\302\277\242\200\032\277U\304\214\277\244pE\r\337T\266\277\214\374b>\331\240\265\277\203\336\234\033We\275\277\262\346-\274 \003\220\277\334Z\306\007|\317\261?ndL\337\224Y\262?=\377\346p\374\312\261?5?\246\322\304\214j\277fxE\267\375\207\306?\242\223\241|T\374\314\277X\221\253\311\364D\260?\374\203\274\022\312\242\241?i\362\263c\2356\246\277\327\320\242)#\373\307?\001o\364\017\226n\255\277\273\321\367a\034\037\245\277\t\273-Cb\244\255?\000)3\0136\322z\277\321\206c\351\332\204\262\277\021\227\356^\230\341\245?\027\322hBK]\241\277\025\332\273o\037\370\243\277\370n\025\243~\225\247?4t*h<\373\267?~\277\373\213b*\243?\326\007;\324 A\261?\266>[*\340\310\251?K\0174\371\213\315\310?\252\007\325\233@i\272\277M\255\253\346\264\225\265\277\243\357\260\252\266\013\303\277\206\206\376\344\346l\264?q7\233\352;\336\310\277\261A\205#\030\207\310?\230\267\325\261\025\t\252?H\214\333\265$\005\227?\324w\225\004\327A\254?|\375.7\326\366\307?x%\227\250\000X\256\277\266\327\214V\003\201\241?F\240n^\214\344\264?\266t\270\322\023\261\264\277\356/\207\326\311\205\264\277\317\205+\211U\367\262?2\376\240~C\370\311?\327\251\234\307a7\250?3\343\363\347b\217\241\277\246\364\326\317e\310\245?\301t\316\3060%\310\277\271\267j\241\251\352\225\277\370\273Z\312\205\357\254?\212\261cm\250\336\271?\205\3052\013\023h\243?\253\357\246\037@\326\240\277V\362)\317vn\207?y\025zD(\211\322\277\256\241y\343\005\376\202?U8\316\331\362\261\254\277\327\2777\241\314S\303\277|UQ\361\351\326\223\277\215\253\262\027H\024\324?U(\236\231\216\366\273?\004\230h\245X\360\326\277\016\242`\273\273\007\223\277kD\231.!\220x?\305+\034\352\272\371\263\277\"\202u\210Q\243\300?\022G\245\240-i\272?pu\271\304wf\244\277\206n\201[_T\242\277\335\014\367V\240\035\275\277\367\262\001\230-s\262?\305\025v8\021\215\301?\330\344\376\251\311s\217\277\251\370\356{y\261\270?\255C)\207\263S\233?\217\230]\334\303\347\244\277\027M\350\2764\265\233\277\223\\\034\002\2175\265?\243X\343C1\002\303\277j\243\362\017\030\007\227?\375\364\364\260\232\221\264?\3653\350\033p\360\251?M\226\244\023\317\357\301\277\374\200\367m\2610\231\277\270\230\272\226\262A\301?\267^@\317j\027\277\277\001;\204J\253\037\212?\300Af\017\006\314`?X\323p\273\327:\245?<\362\374\374\202F\316?\033@-Q\224\303\214\277\025\205k\025\t\277\255?R>g\322M\214\235?\240\372)S\030j\234\277\023\275\315U|\210\200\277\211&\0104r \253?\207\244\"u\252\272\263?4L\342\247\302\302\267\277\034z+\31647\260\277K\220\252\017bn\314\277\037\272\223f\345\314\241\277R\321\201\246\007M\266?`\310\0274`Y\307??\315Nj\211\365\274\277oT\257\222\001\357\234?\204\252\231b1\267\267\277j<T\205\237T\257\277\271\3535J\232j\265?\200b\371\n:\343\240?\343\327V\241\304\217\236?^\330%\313\215\344j?<U\267\321\314\366\240\277\311\010\253\2058c\221?`V\034\333\271\247\215\277a\257\275\216\315\244\242?WFvW\375\353\270\277\335\000\224\371h\337\306?/\230\220\261\014\366\303?\006\232\227,n\367\254\277\000k*\237\237\201\240\277\032\225\237\365\213*\204\277v\241\3440\271\261\275?5\336\326w\021\266x\277/\017\235E\335Pr\277\244\r\nqA\376\273\277\355\344c\213\235\342\261?\237hq\257\223?\305?\303i\275/E\266\261?\364\327\037k\336\000\242\277\366\362\003G]\033\267?\211\321\375\217{_\274\277%\211i\350\221\020\307\277l\243\003s\351\003\264?\327*[\034\245!\240?\371\310\337b\t\303\235?_\323\270jc\311\260?\\1\337u\363\360\277\277\026\250\2125o\030\267?\221\326\204\344\306\212\322\277\3473_\264\240\315\230?\302K\232\225\260V\222?\n$\003\336x\022\303\277\233[\327\272\234\334\267\277x\364z\3506\203\240?\232\024\274g\324\003\275\277QE\244\361\311\351\334?V\372\375\254\261\365\257?\325I\210\370D\035\263\277\3664O\321F\331\250\277x\330\\\2450\237\272\277\356*\207\221\031\222\221\277\251\230*\225\333\320\257?p\274\233\034\252\331\271\277\014\001\330\276\242\341\253\277\345\312\355\247\212\377\255\277\264\337\035`i\376\272?\006p\352?\006p\254\277\346;\025^\026\222\220\277o\251\262\226\014x\247\277X\372\017\270l\235\312?&\013K\\\245\236\263?\340\221+`6\022\217? T\023\243\360\203\307?\217\201\364\227(\375\277\277\270p\267%s\267\277\277\273\362&\231\341\236\260\277\003>\030\332\265\376\274?=\235\343\033\036\364\212?\367\334\031\327\334\335\303\277\226\2573P\217\235K?2>\273\377<\024\220?\204\030\240URv\265\277\307jc\350\013\270\311?\346\026\255\340\267\350\240?\225\335\374\217\206\330\205?\230IP\265!\321\214\277k\340&(\021{\302\277r\350\367%\223\242\307\277\336\021\1773r@\243?\254\341\317\2050\314\200\277<\352%\277\231t\273?\017\207g\205\362\220\221?\254\223\372\260\025\307\252?\017B\354\375\337x\301\277r\032\316\371\213\326H?J;\271\027\313\274\215?\334\300\373\351\243\204\273\277\236\265\203FUt\322?B\205_\321d\255\260? \310\311\006\252L\304\277\007`\323\332GK\301\277iK\332\236\264>\260\277J\234\036\271\374\r\241?\372\330\272^\205\025\260?\265X\027\372\013r\302\277\210E\003\200\375I\277\277\266!qK\246\313\256?\302?/\214\305\234\276?\213\367\001Y*s\241?\350\243\021)Qj\266?\3202XSM\217\245\277\020$m\216\201\211\257\277\242\007\225unWy?\351\3157\007*?\273\277\351\225w\257i*\273\277`&\031%\370g\304?\302\003\334\247\202\277\202?\205\247TF\250-\242\277\026]\022\276\247\340\304\277e\352(\244 y\253?\037\202E\345\023C\233\277zT/3\364\362\247\277lK\212\324\374\355\221?j\263\334\023\272v\261?\347t@\004\302\304\237\277\261\254sJ\\\305\251\277\301\034\306\207\305\031\275?\203\306wg*\226e\277\261\327\225\370J\005\242\277\020\234\026\363\336\332\241\277\324\320\206\207!\216\267\277\377>\204Nh\205\264?ZG\334\273\215\366\252?\"\021\251\002\363\323\212\277\205\316\370\327\210\324\267?\230#JDV@\325\277\252\n\220\"\224=\223\277>\330\310\366J\246\271?D\321|\256\200A\271\277\311\216\247\304E\036\323?]\304\363\333\251L\266\277\255\324\351\307\330\303\266\277\345\355\237\230\277\014\267\277/\247\256\364\3039\265\277Wp\020=\246\357\206\277v\313\246\267!D\260?\253\016\271\004f\361\311?;P\377\006=\223\246?\000\034\367\301\377\244\223?\224\0318\020\312\335\326\277\030\263\032\007\277~\223\277X\337\026\313b\371\252\277x\200l\201,\214\200?R:QH\266O\301?|^\204SS\321\260?6\024V0\027\251\253?\2326\261\322\252\236\264?\232\254\"#\367F\253\277\022\033.\342\325Wd?\177i1\205\233\352\243\277\226\226\362Z\235W\305\277\330l\205\2639\353\246?\244\331(\207N\017\244\277-\215\372\204\207%\305\277\275:\260Y\276X\240\277x@\306\326\237\'\211?nc\307-\234\250\301?\226\343I\3538E\305?\266_@\343H\327\215?K\237\221Vs\271\233\277\302\365\006\211\367b\245\277\030b\214\334\201*\276\277\211\343N\306\350\326\242\277\212\2633\276sL\233?\025\021\377\335\330|\312?\322@\001D&M\232\277S\321jS\tD\301\2772k*T\371F\241\277.:\266\310\314\r\251\2773\177\331\330z\237\262?\212\374\261\314\016\342\274\277\325iTK\363\021\301?\021\212\211\300\201\001d?\3416S\355\031\021\257\277\1770\310\227\276A\260\277\036P\231\344\014k\244?\327\313\375\357\207z\265\277?m\023\370\006\262\217\277\332I`S\337y\273?%\313t\032\254\333\274?\027\224\217\232D\006\272\277\210@\226\357vL\304\277t\264\2514d\036\241\277k\td:5\354\203\277\236\322\227u\214%\206?5`\351;\031\356\320?E\323\232\036A\341\213\277t;A\t\212\006\300\277\355\364w\353\340\022\261?\344\241\276>\364\225\202?\355\017\030%h\264\264?\204\203#m>\246\253\277\211\355{\310\345\376\301\277\351\262\304>\256\246\260?\347\255m\3345\033\267\277\304\037\362/\260\311\313\277\216\371\213\3068\004\202?\233\250\2158\252O\306?\214\364\347\214\'\220\246?\316I\212(\242?\262\277\215\230\372\250\216\301\256\277\036ZTm\342\261\241\277\362V\007\355<\225\302\277\212g!\022\035g\257?\036\270\351\035Z\260r\277\372.\364e^\t\243?\367\200\305gZd\276?\274\324\366\247\312\337\207\277\024\343H\360\311q\273\277\n\025\023S\241\026\320?\243Q\236\013\210\016\236\277~\301KB\350\363\255\277\240\017\214\375\030W\304\277B\265\343\000S-\266\277\231\tt\210\272\013\230\277\014\270Xh\250\"\300?\"\254\371C\345O\256?\266\353n\3013\220\261\277\236 kR\305\324\262\277\332\251b\025\304@\261?\335I\252OU_\314\27748Z\223\255\232\201\277\335\311\232\006K\334\264?\343\230\235Q\312c\271?\245\255f\026\3778\177?Y\212\203\353\244\302\267\277Q\266SO\023\357\276?\362\334\256\240\311\233\324\277`8o\305\233\310Q??@\344\313\226\323\227\277ohp\332\321A\263?\006.q\365\263\236\241\277\206\300XD\276\211\243\277jr)\253\331\003\202\277\236\376\336\365v\034\242\277\252\201\313b\352R\230\277O\363\207\206r}\256\277\262\342\230\312\001\377\303?F>\007\202\347\204\266\277\235\215v\356\376\277\316?/\324\000\177Y\265t?\332\257\203*@-\250\277Y\334\225^\370\225\270\277\247\333]\334\035\375\275\277\022=Z\243K\350\301\277\304\240g\365\215\014\264\277\206\3431^\302\235\225?^P\273]\017\007\260?\330K&\261\270\037\266\277\372c\202\377\267\034\300\277\325\237\257\334\343\375t?\323\026n\036\220\211\273\277\\3\244427\256\277w{\235\241\2178\320?s\004g:\3411\273\277\354\221\035\272\224\027\276?\363\217a\257\231\346\246\277\362:\347\343@\251\231?\342\231\330\244\216\255\235?R\325=\2443\201\222\2771<\235\322\263a\231\277\310\257\335\272\246\371\261\277\205|\375\207b8\302\277\214\\\372*7\034\246\277\354@hS\005\014\242\277R\372W\354c\230a?\312m\234\242\266]}?A\216\224\263L>\266?9\025\220\275\321q\241\277\361?\036\336a\316\302?j\341=\213M\017\246?!\230-\265\376\264r?\201yit55\262\277\301l\253\270?u\237\277\241\024E\306\273\353\221\277\217\335\246P\370\365\253?Z\310\243\002\373%\223\277\221OzG\n\264\276\277\336\r7>\310\317\262?\3144(\241\202\376\231?\265[\360\006*[\246\277\310\347-\345\313\351\267?\306 \024\305\345Fw\277\203\034)I\353\010\320\277E\326\233\204\313\210\241\277OX\334\277~x\274?d\326+\345\221\244\251?\035&0\261&\254\271\277\243\212\210\2059\314\265?X\021\273\277\003$\250\277\027\214\230#\027\t\250?f\314\007Z\224\351\255\277\357\036\221\243\007\262\217\277\300\374\233\346\330\020\237?\247_\217\362\025\354\255?\260\377\007\260\222*\252\277\207\360\245\315\205W\234?[7\321c\003\001\264\277\370c\325\331\223g\246\277\262Z8Y\'i\304?c\224Y\241\367G\264?)1\222\021\231\355\243\277f\212\230hR\274\232?\224c\315Tc\262\304\277(D\031\256E\371\310\277:Rbp6\320\221?L\0348\340\333\226\304?\0262\202\322\324_\245?\327\326WA\222My?P0\243\\\007\n\264\277\301\000F\316P\336\231?\234\301\322\003\034<\323?\261lM(w\367\302\277\347\026|A\323\023\277?a{\244\204\210\201\227\277\312!\256\206\245\242\232?\321\340\031\327V\\\303?\"ld\007>\257\241\277\\\230\222\001`\366\264\277\316/z\365\014>\200?;\350\024)\004j\245?\310\017\222<58\250?\274\325\321\232\356\r\253\277\332_\311\243e\375\264?\344\'M*\236\233\260\277\265]\022\204\276\262\211?\320C.\333\037;\250?\374IHa\001|\246\277\200\014\376T\304n\262\277v\001J\370e\272\301\277\274\334@Q\017\247\203?oS\026_z\272\246?F\317\032\002?=\251\277\r\037\010\244\301R\207\277\250\270\303\234_\351\247\277\203\007G\033yI\325?\025\277g\341T\300\257?\321\233N\026\275\375\250\277\013,\000ZF\025\311\277x[\346\236\372\014\257\277\221\350\340\326\337\271\226\277m\230vcX\275\245\277\260/\343{\341u\263\277X\035\"\317o\341w\277\317<\306\210\035\324\270?\275\0321s\034\376\272?\253O\316;\265\351\331\277H\302\221yl\314\273\277\205\320\331\272\224h\261?\310g\217\212\244k\242\277\354\306t\003\201\362\271?\236B9\013jX\321?\273\237\034@1U\301\277\356&\303\273c\304\246\277>D\200P\267\265G?;<\215\n\247\005\300\277\223\021[#\372\253s?&\357\324\032~\375\306\277\32712\250f\340\231?\'M\315;r\265\242?V\353 V\n\206\257\2776\363$\r\235\001\220?\257\215\326W\254\320~\277\373\236A\344\243\223\301\277\017\325\271\372\233\027\212\277\305\335\037\343n\302\266?M\324\023\265\010\000\313?i7\343\371\371\007\270\277\340\275\310\205\"zm?\350t\344q\261\350\241?\037\311\275\211zU\231?\365$\207C\334}\264\277\356r\222\245\317bz?\234\252_8\216\022\254?\242\003\221$\227\355\275\277w\235\240!I\364\215?dt\333\'\221/\241\277\224\351m\032\241\356\317?Q\253w\004[H\260\277\263v\273\203v\210\240\277\254\341\236e\376{\245\277S\255\254\025\007\352\203\277\025v\005\036.\220\265\277\321\206\277\361\262\313x\277&2\327\335\030\r\225\277\264\371\017^3\314\304?\351N\274;\033\205\267\277m)\210\235\362\327\263?\342EM\014\241N\271\2778\365P\213\345\222\230?QB`\324iD\231?\272Jq\244yz\316?\024GV\271\276*\244?\034b&\232\334\375\301\277\337F\333\207.\016\267\2771\010Ht\216\214\266?\331\024\301\267m\344\265\277 +\237\362\r\307\241?\227\274\202\366CT\245\277\203\307\nF\264\224\242?\323\304~aR\212\300\277\340\036\031\324/\016\272\277\025Z`\276Vr\232\277K\240\020\004\356\\\255?\204<\350\355\330\034\263\277\217F\327\273\264\254\316?/\036j\207\034I\266\277O\265\321Y\231\233\200\277\023)+f\367\334\272\277Y\034\326I\222L\277?f\211.\332.o\225\277@\177+G\027\3002?&\\\270\371r\315\263\277Ddv\243\231\242\205?.I\244\004\273\376\253?\332\301\324it\251\253\277\276\037\034m\345\367\262?\212\214\331\021\221\365\223\277\231\310\3030i\257\263\277x)\360vZ\226\271?\370\205OV\006\213~?\310\356\315\324\003\265\313?\274Mrwe\t\264\277k,\236\261u\331\263\277\242ep.\314+\256?B\362\263\367\006\376\220\277s\252\361\234\0038\322\277}\315\213\347\375\210\241?\366\005\034?{\024\236\277\244\356\225\017S\227\257\277\312\025\256(5\356\242\277^b)\346c\033\241\277s\207h+\234\230\220?_\010&\201\213\314\224?n\345\210~\r\010V?3\340\217U\250\251\224?oC\247Z3\362\313\277\207vw\362\233\212\310?\305o\242\007~\355p\277\263\343$d\254\320\262?~H9\314e\034\251\277\2322BK\264\261\304?\355>.\211\315S\261\277\272V\334\324\364\004\216?C\2209\204\007\n\225?\030\037\306b\205\376\244?\r:@\342\210\037\266\277\221]~\037\305\310\263?\235\033\021f\361L\207\277\272\313\376H\2115\263?\0203\032\325E\352\300\2775\307+ZS\371\250?p}u7\235\021x\277\000LG\231\305\260\304\277{);\007t\204\261?\316\350a@o\371{?{M\251\320\303\342\232?YV\212\333\2526\250\277WGWy9\216\243\277\225\341\354\247W\213\263?\237}\347D\365`\252?:\024\324\305\205F\276?%\007\226\325Xk\245?\313\302\374\335\265W\257\277\3542]+\272\321\315?\320\004\243p\033\337\260?\207\373@\2172tv?\246\310^\364\245)\320?\235\367.\201@\363\253\277\217\364\311\263Cy\256?p\200e\370<\232\301?\307\221\t#\263\374\300\277\026\210ac\235+\221\277j1s\322Fp\234\277kl\231\242\013N\275?\226/:\0301R\246?\240\026T;\n\002y\277\017\245\375\001x\224\267?\371\350Hu\271\266\317\277\223\315\367-\237\262\302\277\237b#\371\246\316\203\277\216Uk@)p\247? U3g(Z\276?\337QM\005\374\tL?FX\004\035\250\232u?\177\003\270\254P\217\244\277s\010\265.k\256\253?\356\201X\347:\310\264\277j\270\037co\217\206?l\315\233\336\202?\323?\345\001w\2665\255\202\277%j\027\277S\334\304\277\016\305\331=\202\302\305?tF\257\266\210\201\256\277\325\247\311\330\363!\261\277$\024\245` \306\237\277\003a\036![\356\300?\254\346\251\241\353\003\260?\201\230%\014\214\226\203\277c\"\315\260-]\302\277\240\364\3640\t\320\313\2773\377g\352\210\304\252\277\374>J\343\014\014\216\277\301F2\303\2777\327?\001\001\307x\363O\303\277\366En\226~\353\257?\336\362\246\260\364\221\265\277\321\031\313\026Ao\271\277V\321\366)Y\340\221\277H[\262L\260&\301?.\243\252\347Em\267\277=\376\031\344\024}\256?\037@\216SM\357\254?|\241 \221\265\302\301\277m\251\307\355\304\346\253?x\250\036\332\351\333\245?L\260fQm\"\206\277s\322\362\206\344\323\320?J\321\212?-\005\211?\007\353\244\312\226W\303\277\370\311Q\n\311\204\264?+T\223r\242\265\263\277\243}\270\256w\252\203\2771\315s>+\236J\277t/?6\331\206\200?\243[\210\357j\351\267\277<\026S\024+e\226?b\326y\344\360S\220\277{\341`\346_\034\252\277\2354\304\340!\002\242?\225\274_d\210,\240\277\017\247\247\240+T\313\277qM2\364\033\272\244?\030\007\273h\024\347\213?\260w\343\252\202\201\256?\306\005\314\021\367N\307?\026\0273l@\202\235?\337A\336\'\\\010\205?d\331\313>\305)\307\277\361\232\031!\344\336\304\277\354@A\031}\263\263\2775\2769A\362z\305\277\217\000`}8:\276\277\261\025\352\252\2575\253?zFL^\311\252\244\2776\010/\276j\341\302?t\353k\351\325L\245\277\330R\263\343\211o\277?\221\223^v\007\362\271?\026\265j\323\255\234\323\277(\217\210\230NO\274\277y\033]\332\016u\235?\305:\360\306\331Q\277\277\302\353\324\245\346\014\361>\327\027*\273Uo\246?9&\264\233t\026\276?\3210\362W\340\303\304?\264\312V5\217\234\232?\230\017\035\301\001\311\224\277\347s\321\322D\260\320\277\n\206\254\315\335\240\263?o\003\203R\177\211\221?Q\265s\0271?\302\277t\020\250\273`\262\307\277\241\342$\362pd\221?e\2504\322.\361\246?\037\361g\312\216\013\251?9\324\004\313U\025\304\277\317\321\246\005\320u\265?\251-\322\256Q\252\247?\030^\022\033Zb\305\277\026\036D\014d\334\242?5\023\347:-\343\205?\347\320\036\211\0330\301\277\245\334r\222,g\256\277\302\022\372\226\212Q\206\277\244\217\032\201\216\223\241?^\227x\352\270 \233?\207m\223\224\215\033\254\277]\345\332P\367/\211\277h\250\023\326\210\346\265?]\334D\201C\005\241?\376\177\202<\245/\261\277\"\377/\216\335q\255\277\267{\007\2108\r\260\277\017\210\023\205\234:\211?\316\214\372\370\310\\\267?C\251\225\277fr\300?7\030\016]L\356\227\277\200%W\265\205\267Q?\306\006\177\362\332Q\177\277\360\374\243\036\032\024\313?)Q\006\230\322\242\212?<\233\0334\313Y\267\277W\342\321\276\305\335\276?A\243\"\350\370\277\300\277\322\200\266\260\034\333\205\277\tF\333\t\003\010\265\277/\260;\3045\337\326?\244\001\314\375HM\264?HF|\255\265\377\251?\306:\312\303\247\370\320\277\023\275\0213\263\201\266\277\007\001%\243I\323\204\277\254\3433\010\314|\322?Ne\360\021\002!\247\277\275.f\251^\230\205\277\255\201\206\336\342#\311?\036\307\255\351\024\332\240\277B\034\203\345\371\232\267?d\376\220t\215\354\240?\3778\036\033M0\207?\311\254\334\210Y\346\312\277\021\326`j<\025\227?\364\205:\230DC\260?\245l\351\322\277\271\221?\217\241>t\371\256\304\277x\264\021NoC\260?\223P\"t6_\201?\367\333\353+V\357\326\277\337\331\325\266g\203\241?\3731\236=\360\027\262\277\253D\002\361\030\366\251\277\203\201\365\366ML\267?\240\357\"\005\274\222\256\277%N\307\334\320\234\300?\0005\240\204\3235\300\277?\016\255\267\362\345\266\277\204\367\016\247\212\017\213\277\177\342^\033\025\344~?\031\321\3169f\377\252\277m+\342\364\211\230\256?c^\370\016C3\223?<\376/R\202Z\276\277\342\326}s}\270\252\277\327\256\221l_\262\300\277>\360\232\366N\377\242?,7\177J\270*\260\277{\340\313\032gf\263\277\t\033\377\271\224\024\274\277\200\244T Q\345\300?Q\032\232\\\203\017\266\277\376\207\035pb\244\233\277e\237a\016\321\364\302?\034\377\307\341\271\376\235\277\037\"\300\032\242\234\270?\277\371\034\350R\263\247?\327F\271o\321\344\316\277\353\271U\213eG\264\277\036\325\224\253\330\215\317\277\364%\346\202\032\370\254\277_H\240F\364\345\262?\360#\026 h\345\270?\227\336\2236\371\306\252?\271Y\351\240%\365\311?\311\336Td\r\202\200?\005N\376B5\231\260?\273\376\242\373\034\221\244\277\016\353\343\220\017\300\245\277\254\333\010\342*\036\256\277hF\264 \324\204\206?\256\323\311\246\323\226\271\277fS#\004\223\253q\277b\257\322\242\247\332\242\277~\326l]\007\326\305\277\024\231^\216\013,\252?\234C\237_J\374\265\277\331m\235\353\'\230\310\277\225\347\\\3331\304\261?\255t\202J\004\311\237?\314\245\211\371\0254\244\277|2\317_\322\232\262?\004J\313kJ\034\225\277\244\374\0109\247\254\263?SoG\320\027\277\204\2771\251\245\246\371\022\262?F\224k\223\330,\307\277\006o:\n\273T\222\277qdi\232oU\230\277?u\026f\024\375\230?\221\356v\222\264\252\225\277\002\024\243\314\010_\314\2772=7[/\211\271?\203\006\r\274\n\243\237\277P\2513\316]\203\312\277p*\265?\204\351\262?y\275\272\261L\356\266?<z\310&x\363\260?\336tG\177[\252\265\277\370\314\301\252q\371\264\277\333k\232oN\317\227?|\332b\342A\310\242\277\320\261U\230G\274\263?\016R\241\303a;\275\277\304-z\253\252\007\250\277dV\353\023\247m\263\277\262\357\374\016\305=\230\277\270[R(\r\"\220?\372\211\365\260\371\225\253\277G\344F>\353\363\274?\245%\2358\303Y\200?\317\221\233Mi\357\225?z\260\020f\024b\251?A\'\034P\337#\307\277I\353\365>\304f\246\277\311\235\353\3268H\224?\003Tf}\335o\303\277\224\350\360\320\020\232\245\277\004o\030\335\247\035\203?\214\001\035}\2635\265\277\236\277C\032[\301\250\277s\254\232\260S\336\302?^\201\220Z\007\002\241\277\306#\323%\304\256\306?&\346\030\372\265\367\261?\013.\342\217\237\264\245\277s`\326\037GO\260?(\224\334\313\002\210\263?\234O\032]\344I\242\277\255\262U\364Dm\251?\366QRc\317\231\300\2772L?\376\232[\276?\313\312\331\240{0\215?\026g*uk\266\265?\314\305D\210{-\320\277\3512\211D{\327\260\277\237\223\235J\334\314\275\277h\n\005\000\325\324\300?\270\206\311\376$\364\222\277sz_x9\266\265?\335o\267\320?X\270?c\231\362\235\025\355\220?\021W}\310\360q\210??\264\242\302z\354\305\277.\245\2067J\244\205?\233\325\234}m<\273\277\274\020\342\201\013\322\241\277A&\353\321>H\303?);\233@\373\367\246\277\321L/\315\305\005\226?\327\247\332\"\331\311y?\013\304\265\311\210\225V?\350PG\207\271-i\277L\3125\260}\356\202?E\372t$\352v\261\277\326\021U`It\271\277B(\264\257\230L\262?n\354e\306R\356\257\277\233bl\251\2655\255?\207\\N\323\366\331\303?&Je\000\303)\235\277\311Lx\275G/\252?uO\0343\007\361\254?\325\365h\205\2171q\277\026\300\"\364\357\021\270\277\3451f\372\314j\322\277\230b\"\204\262\277\245\277\037\205\256\002\231:\226\277\314\253\341g\223F\221\277Z\341F\237\337\234\234?\331\370\023~\325\262\270?\222}\375\367;\334\226\277]\034Y\r\200x\301?\226)\032\311\030\213\245?\271dN\036\352\234\213?\005o\037\311\327\357\266\277\001\223-\367\0044\232?\'\261\017L\354\001\273?Ne\245\2672S\255?2>\274\250\036\373\302\277\006\025\000@\362s~?\352T\332zkQ\226\277=\277\247l\354V\306\277W>\001\236\351w\301?i\025\213\341\377U\201?\213`\005\372\371\243\266?s\266\336a\277\340\271?\252r\257<\020q\274?4\331\205\371\223\344\273?\177\334\2532\310H\307?I\267%\253\021\r\263?\024%\303\025I\321\252?)\346\342\345\304|\305?\320(\264\323Bq\236?\207\375\272\362X)\213?\036\207W\304\006,\272\277\217\251$3\364\304\216?\225!\275_,6h\277/&+Te\265\311\277\027n\331\254\356Y\263?\340(\322\254\025_\252\277*\324\232\005b\333k?S?\327\273!\235\254\277\337\314)?\336R\261?$\336\017G\005B\303?\320N)\300\307\325\254\277^o]\221\271\333b\277\244M\235\211A\301\311?\\\017],%J\265?\'G\036\020\266\224\307\277N\027Q\324\025\233\263\277\216\327\244\277\007\224\241\277\"\251\320\362P\006\247\277\267\2325\320\032h\300?\302\304\037\346\021\307\241\277\216\\\212#\370e\240?\003\372\026\007\020\351\225\2775\241*\322\373=\230?oE\n\037\203\267\240\277f\245\022\240\361d\243\277\347w\360\026\201#3?\210\213\246uBO\320\277\247+\252CK\204\224\277\301\235\366\235\321\010\210?O\377:7\322\245\267\277I\307\336n\031\317\323?\252\337m\014U~\243\277\rP\007\257\024\t\253\277\320\001\374Td\347\300?$\374n\210\361\336\302\277\034m\016\302\306\250\242?\347\211,\342\305\236\251\277\325\024\201\024\024@\220\277\'\030\351\344~\236\234?\n\377\354_\325N\262?\267\364\355\275\245j\302\277\025\0039\235n\022n?\212M\371Jm\022\255?\004\020\223\2434\305\244?G\212[\203\360D\321?\256uH]L=n?\274\211Z\323\010\201\235?\362\242U\032\226\216\310\277\n\355\243\257\216w\274?f_\006%\257\371\262?\025U@\223\221\334\301?\347\372\317z\275\307\251\277\027\"}\236\005\246\270\277\346\320\321\372A\276\246?\220\376h\026TU\320?g\0145\233\341<\246?\020\3662X\317\247W\277\266\254\324d\370\373\300\277\241!~\376\t?\277\277\375H|\263\'\255\222?B\031qC1{\264?\232\315\373\3674\260\235?\024u\210#Y+\201\277k:l\342f\250\275?\305\222\354z{\247\246?\210\313\310T\345\321\326\277\n\177\364\372\227\355\256\277\257\376H\224;1\253\277\350\327b\371\317\303\223?\243\3769\360\274\233\301\277C\037\023\362\271C\274?.ga\373&\366\312?j\343\315<MZ\305?\220\245\350\361/\031\261\277\314sy\2733\020\275?\227j\003\245\307g\245\277\304\373bp\237\236\300\277\367\336\327\224\371i\270\277\201\017\252\312\336\027\306\277 \207J\\\243J\262?\300\353\014\016\200\227\244?\001AvA\206\377\305\277\361\355\321\025\353\231\272?@\345\340O\212\372o\277#9(\317sx\277\277\277\263\313\311\371\246\255?\271\3722\341\365b\302?\336/\377U\354\353\301\277\361y\271\276\333\206\300?\351\274\177\276\247\314\310?QCC\r0\256\303?\237n\364\315\223\330|?\302\247\266h\376N\302\277K\372\355#A\t\220\2772\035\002\216\323\032G?{\n%\346Ko\255\277\342uA6\350`\240?\311\330/U1\007\243?\347Wb\251#s\312?\034\210\243\224}\366\265\277\327\277\034\3377Y\327\277\254\232\374W\230\033\250?\252\243\354\276 \027\266\277\253\254/\304\207\353\271\277M\272^\264\001\203w?,\014TB\262F\302\277\240\365\007=\001\360\301?8\377\377\207Z\235\305\277\212|o\361\246b\221?\021\n\353\305\377\367\274?\366\177\314S]\266\207\277\344\261\251^=I\271?;\226\371\234\217\341\240?\030\322_\354^\347\314\277\370\003\027\346w\243\304\277\202\030\010\260`\347\210?SV\3323\357\t\275\277\333x\363\312\267b\224\277\236=\266\370\332\230\231?\3340\346%`N\223\277\327\202\340\325M\244|\277\327\305zg\370\260\301\277,\341b\002\356\323\262\277K\246\372p=e\235\277x\360\332l\035\300\255\277R\254y:\374\003\251\277/\244\354\370F\315\252?\020\037O\016i\034\223\277\2176\243F\211\222\304?,\317.\000\211u\250?U\227s\305\263\346\256\277\236\350\300+rT\234?\037\320W2D\003\274\277}+t^wD]?\303~\217\004.\350\242?t\0324=\327\203\270?}\"\236\241y\"\230?U\242k\307\356\235\256\277r\221\n\220\002\022\271?Wo\\\236\300!\301\277\252M\362s)\002[?\205\214\242\013\347b\227?\206l\216\251j\030\334?\006\004\233\344\0238\250?}Kc|\207\235\300\277\252b$\303\320\370\267?\320(t\335~f\305\277\341p\372\300\240\034\244\277\357\'\230YW\265|?!\313\256\246y\214\244\277\331\005\001Pr-\277\277\00511^\246\225~\277\376\372\214\260=\264\237?\277*\233\336\260O\210?\\y\270\201>\217\254?\n\2604\350\247\300\223\277\273\242V\365\236\201\260\277\215\331\211\001\272\305\254?\302\362\255\342\276\276p\277\022\275\374\004\377\350D?\243\031\270\263\256\321\275\277\260P\301\010\257O\300?\270\366\232l\275<\312?\366\243<\030~\017\304\277\364PU\3729I\265\277\035.\233\225\023\335\252?X\322\376J\013\344\273\277V\r\026\320\321\344Y\277\232,/\254\216C\241\277N\274v\206\344\307\304\277\231\221\017^\217X\261?\304!r\342\374\207\267\277\004C\371\246\016\230D?\277\303Y\204T]\311?\202\201\261\363\203#\247?\365\333.\021V\350\246?\314\222\231\233\213\001\257?\340Lk0\003\r\302?:\362:T\257\036\207?\"\310\'+\366rs?\230\257\340\260v9\255?{\022\333l\344\252\235?\322\'4\024\204\002\274\277r\200(K\301\314\260?$\010\310rOy\273?S9X\256\263A6?\206\010\025\222\212\rs\277X\317Y\"\"\353\277\277\304\345\340p\033\177\270\277\3716\2138\247\322\240\277\274h\013\315u\035\252?\255\344\227\225\220\016\234\2779\245\315\201\2029\267\277\013\350\321\374\340<\224?\240!\346/p?\274\277\224\231\263\007\341W\241?\t\322\202\314\275:\270\277\273b\206\253\375\244\261?\223\n#m,\221\300?\334\225\\yB\274\253?\217>\\\205\314\225\242?B~\337i#r\230?[\205\014\036G}\264?\316\264\271C\367\303\260?5\275\257n\235\304F\277H\'s\256\311\275\242\277\376\355\204\017\334=\213?@V\277\362z\273\264?\324\031\354\344\277\221\251?\252\341\312\3513\225\242?\023h6\035\320\026\236\277mK\014&\\\031\202?\334)\226K\034\n\270\277\265\027\241\347^\305\260\277</l?\250\204\254?z(\3726\001\374\230\277\017\017\371J\303\217\266?\reM\352]j\254\277\374\310\2054\267x\253?\342\242\327\014%\236\314\277\342{z\366\260\006\243?)\303\326 \030\372\232\277\032\355w`m$\317\277\213f\205\264\\\230\255?1\323Y9\215J\261?{\3219\013\212f\306?e(x-;w\242?&\027\233\222\022\001\250?K>z\365|s\271?8\024\364rI\311\216?\342\331>\374\263K\304\277\277\267\341\000\0344\301\277\372\212\033\202B\324\202\277~\\\3756\t&\305\277\317\242*G\235\033\272\277\026\367\204\331\005\t\263?\305v-\237q\273\257\277\204|Ci\017!\301\277\243\316\020\310P\200\302?\013y\323\300\240\363\320\277Mxm&\t\253\245?\366\323,\243V\264\242\277C\316/`6\\\226?q\217\335\234\201\211\251?\003.r\313\311D\304\277a\3550\261\202S\261\277>lNZ\252\177\245\277.\3524\005\337w\306?^\223\002\010G\377\234\277\017\267\007\214\265\037\272\277\335UTu\0270\302?\013$\013c\230\367\275\277\267\252\224\027W\247\273?2\004\332\303\274#\310\277\301H\370\300\361\221\324\277&\264\257\336\302i\242?\027\206\243\303\224v\244\277f\to\350\333\277\276\277\305\262v>e\255\213\277\014\003\014\001\306_\202\277=\024I\007\365y\241?\003\265\370\242]\347\265\277\206>n\266\243\260\267\277h\305\265\263\222H\270?W[\316\355\0331\320\277\003\2770\320\323\020\277?:\372$\300\356\375\275?\335\357\304p\264\307\301?\233\316\204\237\t\212\303\277\205\n\025\271\254&\202?\241u\261\223\254\220\260?$\315L~\361\315\246\277\205Gn\335\001L\240\277\032\226@\3103x_\277\2460\304\261\306\343\230?\345\214\353\342\t\'\232?\362\201T\267\212\342\242\277i\3622u\237\217\235\277,R\013gH\021\227?\330}\0270o\341\242? \351T0\355\001\247\277\001\234\036>\325\222\223\277\225E>\342\312\375\306?\266\372\344&\3000\241?%b\220\326\325\270\301?f\220\004h\230Y\316?$\021\013\352\332\032\212\277\300\205\213^vgu?\t\271\314(\"k\201?%\031\177\233\010n\222\277W\000\252\354\345\354\271\2775v\262\243\351\224\231?WbP-\271#\307?\331\220\271\341\246$\266\277/\345~\3548\010\222?\376\372\343\204\\\340\246\277\275\367.\210\330\311\303\277Oh\37545\350\255?\256\355\262\021\003\n\250?*\002jn\r)\264?\262F1\003\327\r\305\277\207\354\372\203\302\320\311\277\272\201\277\343\334\247\266?\005oa\177S\311\210\277\341\016I\371M\231\217\277\252\212B;1e\231?\262l\010\260\207[\272?\002\270Zk\267\244\216?\207\301\262d\363<\265\277\263 \301\212\023*\302\277\322\357/\330}\252\244?\022\220\"\353\2643\262\277\017\215\222rB8\237\277Kk\rO\353\020\256\277\241\025\014\226\325\254\302?\360\234\312\305\252/\247?\232\223\306\236\237\212\240\277\rf\270\236\322\221\316?\251\242F\304L\324\202\277\030\242\357\3618\206\243\277\237Tc\262=6\321?a6V\020\004\371\245\277a\336\0343Y\025\241\277S]i\250\201=\306\277\225\023l\202U\017\311\2770]\343\316\006Y\217\2776\014\034\010\\dc\277\216\312\'\266\3574\310\27771\335\242\214r\274?a[[qL\347\232\277\374\326\024\214\177\310l?B\250@L\372\014\237?B\\\340z%\321\210\277A\222\345\261\311c\262\277\003\315\315\366T\204\263?j\345\246\031,\377\216?\022$N\343J\304\246\277G\235|uta\214\277W\302\263\021w\216\324?\242\304\211?f\370\264?+R\003\306\206\247\303\277aGL\255}\266\257\277\'\242\241\3234_\260\277S\253\343\331\371*\244?\272]\272\037\262\305\262\277\316\243&z[\177\254\277\004s\375/\271.\211\277\341|l:\231\237\243?l\341\2717\276\\\205\277\320Nc\263U2\271\277p\226\r\0078\224\253\277\310H\221Si\354\266?v{\033\277\\\337m\277A\251\220\234zJ\257\277\276d\'\276n\236\265\277fe\267v,Z\272\277\033\245\0039\"S\222?\346\3279*]]|\277\035\035{\237\002]\235?\313\305;_\243|\270?\213\307&\356\225\204}\27722\345\255U\000\262\277 \227\027\356\022\212\270?\250\257,\353\324\017\272?\310_\271\225/\255\236\277\205\313\312v\2468\275?\301\226\210\315\373T\307\277S\353Q\227\346*\264?&\002x\244H\234\260\277\213\024&\203k\000\300\277\355\003\337Jm\222\270?\242\201\335\260\327\225\275?T_\275\314\256\235\232\277,@\377y\242\217\307\277r-@0\024\020\236\277\201n#\324\204W\273?G\234G\315\212\251\302\277\254!\317O\303\035\241?\327\234\324{QM\273?\267\2502\202h;\260\277\216\r\203\243\262q\213\277\364x\255}\000I\001?\202\316\261\031N\014\264\277\3760\030\315\006\235\235\277\255\206Y\277s$\221?\262\025\247L\240S\244?\023\340\226\013\343;\315?\006*\014\000\324\302\270?\302\324\321\265A\310\262?\351O\003\366N\032\261?\223\000\311\257\257\343\253?p\034\201\247\227\250\235?\\\272\207E:4\260\277\326\337#\323\357\304\251\277~\210#\223 q\310\277\375v@F\346\345q\277\223\001\001\322\251\031\274?\366\261\241~p\237\302\277[\212\363H\337n\214\277ym\274\265\203\211u?\033\244\365\201\215\003\260\277\341 \tH5\302\301?\244\000^\331H0\241?K\331a2Cs`\277\306\261O\001\2422\301?\224\341,\247\363d\303\277p:\013\224\311 \262\277@\214\004\375\243`\301\277\177k\264\310S\362\201\277L\370\360G\325\177\270\277\030\200R\271cG\274\277\361e\022\275\254\035\272\277\007\236\265\207\337\227\244?\245\241E\326O\006\266?\246\3436c\005\305\234\277D\th\003>\004\243?\r\322\215\322\323he?\232\315\263\250\223t\261?\252\272\363\'\362\357\251\277XceAS\014\241?\347-\335\265uH\312\277\217n\223\310aF\255?\355A\002\341\n\222\220\277\253\037\272\3764\301\251?\345m\343[\361\321\255?\332\371\313\240.\300\276?\013F\034\014u\241\243?B\335=i\006x\275?\255\345\375a,`\275\277j`!\304\005l\317?\252l\246\014\3272\257?\341\350:z\013`\255\277\201\256O\202\000\001\265\277)-[\272\347U\254?\2748\211|\320m\305\277&[\r\256\316\374\225?\352\023\375`\372\302\314?\314oA\010\nZ\261\277\240\3018D\033\000\256?c\337g\330\021\033\274\277a\3616eT\024\271\277\336\224\213\30327\243?oP\356b\264\213\252\277G\204\310\331\244\324\300\277s\216KG\321o\234\277\353_\364[\325s\266?b\307\243P:\366\322?\321_\002\323]\300\263?B\250\2401\350\365j?0\215(dU\215\265\277\367yU\376P\355\310\277\270\371\247O5[\241\277\tz\0374\260t\270?\352-\373et\337\273?\001Z\3254\317\027\236\277=\260\344\373\033GM?\234\235\332\312\236\364\251?\357\246\205\314\243}\264?O\000D<\350\014\257?\021z\256\335\226\034\251?f\301\256\207,%\312\277\264b\334\355\324\317\246?\025\203\023\353\030D\266\277\227\340\006\200\370\363\252\277\n~\275&9i\247\277t\327\205q\313\341\251?Q\216n@\223\266\274\277X\333\306\305F[\220\277\220\221;: \252\305\277-\203\375!|I\231\277\375\225(.\247\314\222\277\230}SY\276T\270\277\020\277\352\264\245\\\266\277\376\316\247\236\243\213\215?\260\034U{O|\252\277\003A8w$\033\250?\002\036\262\267gk\274?\331]N;z\261\262?\356\346$\362Vb\303\277-t@\247\nR\265?\214+\370\334\022\340\210\277\245\332\017\240\033q\262?\211\325.h\224\366\253?\334\203I\341\261s\225\277I\257r\010Kc\251\277\347\253\016+\324\247|\277\0075\360O\340_\260?S\332\205\363U\340\275?#\232\310\370d\t\205?\014\013\333\030>7\243\277\022\006\244\312Ew\260?%\352\343\237Bu\312\277\010w!\323\242\353\273\277o\274\250y\362\256\255?r\367\254\373G\035\250\277\273\303\006\017[\261\266?qQ\233u#\224\264\277\260\030\312\004Dd\226\277\204\221\372\210\332^\235?4lls\212\020\247?\021\366\261\000)R\256?sB\302\301\t\227\244?\271\254\303\322\3160\267?\025\017\344z\364I\227?a\024h\322\240\010A?k\254\222\013}\203\270?\023X\207\203\245a\323\277\030xN\362$\301\241?\206\337\263\362\363\375\273\277\252\347E\337\276\017\265?7\002\003\217\225\211\244?\276\270%\277\342\222\227\277\314\303f\316\024\323\245\277>^<r1\211\206\277\022\270k\007&\371\274\277\206\036_\316\223\025\267?Dk\300\240\036X\273\277\031<+\205\343\322\245\277\326a\002\027f$\246\277\341\217\323\314\262\000u\277$}\261\016wN\326?\251\374e\3216c\223?u6n\037\303,\260\277v\260\346Zx\214\266?y\002\252c\300\027\305?\224O\022q\221d\257\277\351\250y\021,\275\244?\235edg\216+\305\277[\360\035a7\264\276?q\312\323\036\212\212\262?\224R(\314\243k\311?A|6\023\216\004\252?\325\275\237h\312(\300\277\276<\346\244U\200\257\277\212\351\326\027\005@\321\277J\001S@\352\325\271?}\266kM\372\233\252?>\036\230|\373E\301?\207\353\367a\247\356\263?\327Y\320\334\006\312\225\277\326\024/N,\340\234?kL\370\203\030\204\275\277\025\237\376\226\231\233\243?\372\247\261\275\206Y\247?\376\243\332\322\334\237\265\277:\340\024OQ\205\255\277/\267f\357\\d\253\277\204\t\376\266\346p\317?u\226\254\203_\246\312\277\000^0\227o\030\220?\362G/\265\325\312\310\277\010\337\235\005\022\373\247?\370\311\201\300\224\320\262\2778\203\010\216\303\203\254\277\262L\245s}\233c\277\020\224-\213\351g\230\277Qk\311\236=\353\272?}i%\367\270\373\261?\250\315\t\360\017n\300\277\t\374\331n\0014\263\277\002vH\332\335h\257\277T`\277\327\335\277\253?\256\212\364O=\231\320?\215\010\r\211\264f\247\277S\246\265\r\243\004\274?oT\276\003\302T\303?\226\317[\204P?\216\277&F)\367q\367\261\277\235\212\242\2447\305\300?\222>5\000A\261\246\2779\302%u@H\267\277\233\003\026\314B\035=\277\221d\223\036B\027\322?\346m\235\352\305\242\230\277\364\312\333\031\023\001\251?\001\351v\365\321\375\330\277\327\377\n\363\240\030\257\277\251\t\357X\200\332\277?\377\340\334\235\275n\266\277)b\177T\246\375\313?\031\346\375\242\270\327\260\2777e@\250\0374\305?9\014\214E\320\254\244?\211(t\2769I\265?]\000\003\332)\t\253?\354\317\216\266)\367\212\277\034@\245\021\344\301\305\277\014\240\331\245\363\357X?\344@\305\341\"\305\263\277\353\220\240<\375\312\200\277\225\3210\244k~\317?d\337\2651\343\227z\277\331\351\033\201J\232\273?gZ\033\214\261\177\265\277j\226\324#\314K\304?\204\276\205E\315\035\301\277\242\020[m\366\343\300?\261\241\374\221\010g\311\277L8OtY\253\257? \253\373\212\256\023\301?\206Mt.\316\002\312\277c\005CW\210L\262?\253\203\036\204\224\324\235\277\023\243<\357%,\255??\375\367l\351\234\272?\350\206#\035\346\203\201?\357J\244R\322\315N?Hb\255E\357yJ?/\347g\251/\t\304\277n\350\362\004+\373\260\277\200\364\315\206\322\022\270\277-\351@R\221q\310?l\222\201\274\266\313\254?\305G>\265\027>\304\277T\307Z\364\215\221\323\2779\352\203\315\277s\267\277\267\350\177\230\216\004\270?\346\377\326\234Qi\260?U[\247\210[\204\310?[b\375\302\032\223\246?\350\202S`B\334\300\277B\317\241\210,\002\264\277\323\366\220g\225+\305\277\356qr\321\376\275u\277\307\225\245L\365\r\254\277Y\364\375\343\314\213\264\2772\234\224\355\333\017\241\277\262-\035\377\336~\227\277\2631\264\277\342\354}\277\262\276\211\007\214T\247\277\276\344\366Hr5\265?\260\266c.\344\341\311\277\301d\3461\224S\245?X\016^\373aF\220?H`\314\"\017)\257\277\027\2504K$I\242?\r\231\271\r\262,\251\277\\.\221\305\217|\266\277K\022\242\004\272\321\275\277\2615\022\rMD\270\277;\236dWo,\235?rD\331\"b\253\274?\373\332[<\033\372m\277o\221\006\372Y\202\216?\343\335\321\324Zj\254?\024`\235\014d\304s?.\237\355\331\271\211\306?\212\363d\242Y\360\277\277\223\220\0249\212\013\262?\327a=\342\220\003\300?\001\233\225\237\242C\300?\310\235f\2713[\233??xp\361*o\235\277y\326\307\001\257\226\262?pj\361\013$\002\242\277\001\252?=\006t\236\277L\'\004\"\232\243\302?\251\336\343\253\301X\226?N\357\210\206M\311\240?\034\240\325\032*\341\252\277\274\203\034\027\335\n\242?\226*\206\341\342z\301\277\006*\253\371\211\320\264\277TV\247}\373\027\245\277\177\266\020\227L\000\214?\347\262\310z\252\262\226?\026-\333\304\270c\202\277\003\200\031\370\023m\223?\336\300\332\211\r\207\274?\205\037\230\021\321\333\250?\271\267-\256\255{\320\277\216\307\367\371?o\207?\274\350\300\217\332\241\244?%^\304\301\204\002\262?\335\375\337\372#\027\302\277\016x\003\022\267\004\231\277\177tA\303P\324\272\277\252\013\353W\3768\306\277#]zs&y\251?\320\2468\342\327\250\256?\330\222gI)W\272?\217\344\214$\246\304\305\277wg\367_\346\246\260?4a:\215\226\343\264?\030\033\341\177%~\223?\362)\307\324\035\025\207\277\017\257\252A\357g\244?\306\227\201\316KW\273?+~KAf\246\312\277\353a/\010j5v\277\0046,vJ\322\276\277\037\004\022\207\362\357\207?J~\316\236z\322\251?C\241\327\"\375\t\261?\221N\361\277\356\310\266?\231\202\224R&\242\245?\245\320D\261\001\310\253\277\"\203\031\354\n\322\262?\351f`\376\035\037\306?\316F\352\233\347\022\265?y\365,\217\223\023\273\277\320sQ6\353\034\251?\203\311\t\304\301\221\325\277\335[z\353\351f\240\277\212br\n\222\020\260?eD\t\226u\002\312?\205\230\231\272\371\314r?$Q\017\026\370_\261\277`Zjy%\350\301\277>=\337F`\215\233\277\020\302R\317\305b\255\277YQu\027\254\223\262\277\021Ao\362r\037\257?\375\361\233\204\374\252\203?2H\345\223\374\341\253\277\t\3776\312\013\002\261?.\237\252T8j\265?\372\260\331\344\231\251\227?\331\247\242LS\254K\277\340\004\330\035*\217\242\277\\\315\276\333\226(\257?9\024\270\013\335\037\221\277+\354@\313\267b\206?\226\356\177\311l\357\262?R\r\373&wy\221\277\231\272A\373R\231\250?}&\306@T\214\260?\252#\024O\224\350\256?\257\330M~~g\267?&N\333;W\274n?\3173\300\302aa\322\277\356\372,\235\213\237\245\277\276\000m\326\255\355\266\277Hb\037\3318T\203?\250\366Z\217\005\273z\277\202\224\035\243BH\271\277\363X\347\363iw\230?-ig\245\377\232\265?\345\277\321\002n\362\241?\362C\207d\357^\226?\327\365\270\226\354\024\201\277\021\244\006v\037\311\260?\256B\253\3378B\306\277\347\322\256\317\222_\307\277OYG\216M\344\326?\254\372\354J\t\020\271??\177\274k\032{\215?\037\325k\265\237\'\212?\361\336\270\032/\320\226\277\027\375+\237\217\272\204?8\326\242kYs\247?\017\203\207@\321\221\265?\013\024\247\035+V\203\277\030\242\036o\231\300\260?s\233\3071\327b\277?\323u4]\233\240\267?Lf\323\2253h\211\277\023\\\207w\315i\257?^\214c\374\254M\311\277m\352(\333\265@\214?N\217u\275\276\321\235?61\374\341\314C\270\277\004c\224\212\r\017\277?\\\242\307\001\357\304\237\277\031\232\330\373\t\033\272?q\250\250\226\"\017\307\277\225\020\334\305\006t}?\021\t\'yW\022\211?M\372I\324-\235\261\277\001\2304\347Q\337\302\277\344\007/r\332m\264?\003n\244Ij\337\277\277e\203%\273h\264\204\277\236\227\262w\255\246h\277)\374\013\351\201\360\270\277\267\265*\325\337l\273\277W\014\356FRx\272?\026\017Xn\253\016\220?\223\276\004\246\221\364\314\277\253\'!\022\033\266\266\277\322\001\253\262&\212\243\277\245\334\224tX\322\232\277]\202\241,\374\273\254?r\366A\264\243\005\272\277\377\277\030B9V\210\277i\312\0068%\274\256?4\000\037\253l\272\330?\014\217\373P;`\251?\231\204668q\246?+\330s\273\3548\220?\001*u\3609\244\250\277\236\207%\006\317j\245?\322\233t\373=\n\255?\253\033Fc\003*\313?\371\323\345K\"\215\244\277\202\224\223H^\225\236?\337?m\010\323\330\216\277[-\376\026\230\236\263?\252\260\350\344<\244\277\277\211\313\307\303\301N\203?\2325\027\370\243R\304\277\330#\367\\\031\223\256\277%\234\265\205\327\374\226\277\335\357\211\244\352\211\251\277\026lg\304\304\374h?r\310\370i\371\315\275?\336\244\323\024\243\337\267?\203\376\207i\224q\240\277m\246\350B\331\t\232?\310\365\311\322\313\340\252\2773\271\361\356Q\376\260?\337 \367I\213^\253\2778?H\267\213S\243\277T.\024\265\035\230\256?\340\254\371\257\351\334\301\277G\267\374S>\231\256?\n\037TtP!\270?$\247\023lK\221\234?\304x\233(\274\003\264?C\325\017YGBk?\214\257\207\252\203\365\272?#.\227p{\232\274\277\345\242\220S\353\221\206?;\231$\310F\334\272\277\345~\270c\323\213\274\277\346\034\275\221\254\031\220\277\3631\340\034\311\324\236?\014\344i\330\210\007|\277\375[\265\321#h\241?\214\336Ym\301\t\272?\310\'\203f6\374\253?Q\373f\313\3563\261\277\344\301\300\337|\332\273?\342\250[t1s\202\277\026^\325x\223\036\300\277\370\365#\274M\365\315\277\004W\310L06u?n\311d3\244\370\264\2778&\241\221\220\223\317?\276E\tf\"\373\240\277X\2150\324\373\255\247?\356\3233\322\302^\223?k#\243N\327\203\242\277\375\001?\216\320\225\262\277N\332\037\\{\321\272?\252A\311E2\211\244?\246\332\300b\321\244\301\277\001\302\337\357tH\263?\006\177{k\014S\264?O\037\261\027\001\233\274?\316\020n\261?2\243?\245R\211\007\r\300}?\260u?&\335\212\255\277IF\311\005\2740\275?\206\331z6\236T\247\277\216:\240\271i\246\312?\031\244\307J{\347\306\277^82\\\310\301\262?\200{I4<\311\263\277m\207\006\323Z\362\266?W\322:t\270\370\270\277\277\000\230\307z\323\245?\347\005\317\337\351\030\271\277\r\373\346\211+y\265\277\216\3667\217\351:\266\277\300\353\004-\007\200\257\277$^\210\375\266\331\203?\033\342\'\251\031\277\222?l\225\335\337\206\275g\277\361\223\364\020\370p\264?\241\177\316#\363T\271?\276\013\035\353^\243\217\277\203y\302\243\312Mu\277\371 G\256\337\304\225?\306\242\200xN\010\260\277\256\220\005\003o\270\245?\256\362\340\261>K\305\277B)\325\217f\376\234?\211\020\256\343!?\257\277\024\217\002\004\251\017\262\277@$u\266\301C\247?{\310\266Nj\341\246\277N\nv\330\322\211\247?\253S\201q\243G}\277\244\'\024\2672k\232?\3159\312\366q\331q?\217\010\023&\231V\241\277d\006c\025vu\261?\215\203\2726p!\321?=9\366j\2271\227?\260\343a\263\034n\256?J\254\304\267\360E\321?\376o\213\037\263C\274?\310\013KDrA~?!\224\343\'\333\221\274\277\266s3\200\236\305\240?LK\336@\025V\257\277\224\316\373\336\242[\303?\304\340\276\326\250|\215\277,\272\001OW\320\254\277\374\013\004\n\312\021\261\277\177\'<\001\024\367\241?\223R\'\343\313\204\301\277\272\223\205\223\340\366\223\277\t\014.\221\336\330\206?\206/9\037V\"\256?\344j\276\003\371w\232?N\000\363\372R\027\207\277\002=\356n{\372\314\277>\256\300\233f\245\253?\325#\244{5\315q?\027\205\021\007\371>\210?4\220.\013|\231\325?\275t\310-\036(\261\277H\215#U\215,\256?\016\327S\322Z\n\307\277J\t\'r\013T\301\277\031\030\244\372\234\240\270?q\265P\333U\204\251\277\215\304z\342x&\310\277\273.\324\000@\356\256?\243r\375\007~lv?\033\3438a\315\202\274?o\223\026\232\310\225b\277\202uD7\344\351\266\277\375\3334\210\304N\246\277\370\024\250\031\347\235\312?\020q\2719\315\371\247\277\206jd\244e\027\213?\260v\3266\222\217\307?\364\312\030\313\033\260\262?\177=\264\327Ux\261\277\200\350\312qc@\304?[H6\213\263*\273\277C\311\024\265i\266s?\336XCu\254u\225?\'\212S!\273\244\262?\226y\023XY\275\213?h-T\261\323\003\250\277\234\366/B\006\016\270?\263\257\254X\272k\311\277\241\007\243\346\'\r\235\277\245z\201,%\035\270?f\360\327\003\2300\205\277tJ\221?\323\355v\277\026\204Z\000h#\251?D\322X\223jY\225\277#n\357\275\031\354\312?\251\200\025\220\213\356\231\277\254\034\243-\2240\204?T\360\177E\237p\267\277=\205\360\235\310\266\262\277Y\260iT\337\314\250?\320\361C\353V\200\273\277\0060\020+\264\306\304\277\021\3360\257\321S\256?G\016\314\276(1\225?\332\237\2070?j\244?\265\032\311,\300K\277\277\367\337B\340\247\004\252\277\245\370=h\327\357\225\277\343\341\032\023\203\026\264\277\030`\227zM\226\261?\037\375\361\2173\303\243\277\360@\336\331\204x\304?^IE\204\n[\256?\206\261\346\266Y\305\275\277Q\323\332\341Y\366\261?\266\000\200\262\t\351\260\277u@|\213N\311\216\2777\231\315\031R|\253?GW\336\\\331\266\265?$\354\021\036\350\311r\277\006<w\315G\325\255\277\035\357\254\205L\360\225?\365\337/\272\2222\250\277\016\200\352ou\345\254\277\007\302\270\377\214\367\220\277vrTG\274\305\205?\364+(\255\375\275\243?|\200p.\032u\211\277\344j\377\276\274\251\200\2771\037\000A\352\025\274?M\220?\273\372\225\221?h\2544o\020u\224?\007\'\237\257\326\366\261?\031\307\007\242\272\016u\277R&\'8!\035\230?\373R\222\204\261\"\303?W\023\314\206\0257\274\277\302\226\023\034#\244\304\277\242\231\353\264\370\313\301\277DX\307\354R\305\305\277G.L\326\301\316\224?\333\1775A\031,\203\277N\006\243\310\202l\303?\305\346\354\304\276[\207\277\031\262cdd\231F\277oK7K\343\226\274\277\256\207\236\263|\357\310?\342\270\333Y\000.\246\277`\321\2547\345q\211?\341\370\325J\033K\221?*cf\260\354\017\305?jy\271\245\250\347\256?\271\343k\346\345\376\252?M\255#~\032\304\325?\324Q\347\362sz\266?F\242\203\240=\364\315\277a\317:\250dQ\233\277\221$G\360\3356\253\277\330;\241\3773\270\233?\345\275xFNc\263\277\335\226Z.HJ\256\277vu\255v\333\220\271?\266/\231\227\232\242\277?\316\321\255E\346\210\270?\033\360IP\303\254\301?\n\\\243\350\017\244\253?y\311fF\353\351q\277c\262\205\205\0332\242\277\177\335T[\336\245\250?\345\030\032\032xh\265\277\377\225:\217oI\276\277\332H\341j\006N\326?\"6\336\002).\302?\325\315\201\276t\225\200?\250h\341H\363`y\277\345\365\371\313\001/\316\277\375A\241\347\037\206\301?\020\261\244\344\217 \305\277\205\177\024\362;\374\307\277\351\342\230\271\370\207\244\277\207\270Oq&\314\276\277\313g\240\331t\237\300?T\243\375\201\0271\256\277&uh1\356r\266?\343\217\332\214\306N\227?/:=\2657K\240?ArD\rQ]\262?2\033\311\364\254S\251?\334J\020(:\344\246\277&mIi\267\362\255\2777J\233\210r\031\266\277\333\360\212\330\342\336R\277\\\341\343\351#\023\261\277\020G]\357\221c\214\277*\360 \303\263\226\270?0\261}X\215:\255\27701*\364f\000\214\277\325\326\346\0311\230\270?\030\320n\321d\232m\277\033\253\346\326+Y\271?g\315Y@\222\271\227\277\227\236W-\344\242\265?+\034\346V\273\341\223\277\037\205\221\240\222b\245\277\\\026\213\034\313\222\222\277 _Y[r\250\241\2779\276\321\302\005\n\257\277eW\231&\206\207\273\277\001_\314\355E\276\300?\034\356\311NB\356\315?1y\311\374\202\303\270\277;\3206\036u,\260\277\355\362u\341=\201\275\277{\005\271v\035\177\260\277O\2705P\221e\247?^\227\233\006\221\207\275\277\204\000i\224\253\221\262\277T\"\335\007\210I\253\277\014\010\241<\t6\300\277HL\322\223\366\323v\277\233h\035\221f?\322?4\342\321\275\016\236\261?0\\b\237\206_\264?\223\rB\266\345\017\216\277@=\254\356^\256\207?\202\0219\237\377,\257\277\035I\031\251P^\277?\302\276[\372\351Ry\277\233\206\256u\332A\246\277\270A\336:\245E\235\277\304b\333g\371\361\301\277\344\030\371\345\2078\241?`D\345:\325\023\261?\340s\315~\035\241\264?\177\217\227\352UJ\270?\364D\'\3420f\255?\372\253\306H\275\263\251\277\310!\376i\250\355\301\277\207\361\321\016\221\360v\277\355\030\336\202\205\233\215?\376\020\033\216y\236Q\277\337\334\225\370\334T\231?\007\313\207\324\352\004\227?\340l\357\360o\250\305\277\243\251f\257\265\346 \277\317l1,\320\266\265?\270\264\t\225\305\030b\277zI\3003e4\271\277\202\002\236\266\\\226\250?\377w\254\r\367:\224\277\364\030a\220|\020\242\277:H\202\246\230l\235\277\232e1`O\261\230\277I(\255\033k\333\255?\251)D\217\r@\303?3$\340Sy\267j?\236\326\264\217\2128\260\277\263O\365\236s\375\215\277\317\010,\340HQ\226?\204\3502@H\224\243?<\033d\017\304Q\272\277\r\324\377\001\257E\220?\356\270\033\213\240\366\270?\251\225\255!\r\262]\2772\363\177\350\352za?\032\346M\357\306\001\317?\361W-\373\227{\243?\365#h\256C\020\322\277\241]\356\263\205?\243?\231\006\205a\350\022\323?n\247\245\202\352~\207\277\007\230|&#\242\307?\231\234\334\316\000\217\216?\240\177P\227\351\256\273?\211ls\306nh\244?d}\354\363H\003\252?\321\342\300\031\316\273\305?\352\326\t\313N\270\223?\205\360*\325_\212\236\277\027\303$\254\220o\256?\350\275\010\310\024\252\250?\327\032\276U\001\016k\277\"D4\304\377\025\302\277\013\327\230F\363a\327\277\002\243g\261\016@\233?\004g\240v%\000\267?\314\323Vr\014\375\264\277\020\226\032)\207c\277?*\304\230\311\333\313\245?\026`\275Y\231\234\273\277\357\337\352\245\237~\264\277)\365-\r\305\223\251?\356tQv\323\225\244?\256\360P\305%>\177?,\253\240z\341\232\261\277\306f\311\360\312f\274\277\357\260\031\245>\315\311?$\270w\242!\007\253\277\271\023N\r\372\347u\277\237<\365\305\330\341\244?\207\023\247\257\352c\232?\227\207\247\245\314\211\305?\0326&\377#\254h?&u\234\357\242-\221?\365~\255\000+I\251\277*~~\343h\034\237\277_W\211\207I\366\235?\203\223\210~\345[\224\277\232x\345\201\026S\260?\225\206 \222\224\254\271?\222\342^a\332 \274\277\365\324@\004\355\212\310\277\376\2614Y\2360\265?\351%\321v\023r\304\277\275r\300\217\355\370\247?D\363\255,\223\213\312\277\220\321\307\244\267w\220\277\236\362\341\345\027\026\270?I ]\367\034\335\247?\377\232\221\002\'\314\240\277t\354\244\356\234\275\274?\362\365N\'\323\310\310?\326\205K&\177N\244?\025\226e\216\027\035\265?D\217\220\374Y#\265\277\237k\311!-\371\307\277\031Tu)S\375\237\277\241\322I\3314\341\231\277CM\331W\204l\300?\356\344b\340\0072\256\277qf\036\356\333M\242?t\212\222\361\356\200\301?\213VA-d\000\243?\234R\016M|\004M\277\223\275\217\035\263+\252?y\317**\344\252\250\277R\212\306\307\245\372\207\277\027\316\010\204N\271\250\277\374\303\"\331\026}\245\277\311\351\363\303\260\266\223\277.)\005\333\026@\247?\305\036\024s\256C\304\277\330\"\2368g\253\272?\2050sr\334d\241\277\272f\273\203#\337\262?L\026j\224!fd\277\330\203\330\243 \333\310\277|9KL]\274)?\"|\031\357\217\216\270?k\336\357\000\370}\242\277\3015\027\3541{\254\277N\317RT\222\235\302?\317!\237B\200\030\262\277\376\300{\302\367\317\255\277\341\304\034X\315\247\177?Q\336h\344\3670\261?\224\312\242\'\300\017\250\277\340\254\334\266\260r\247\277j\211\025\234\212g\273\277T|\207[f;\303\277`\365j\031\360<\305?\2064-=iu\267?\004\226\273\027\353\355`\277\3635\370\232.\252\302\277A\201\025\022\'m\262?\234 \203\363\'\247\275?\247nPg\327\361\307?U\247\224.bY\274\277\2235\371\257\005s\262?gkr\371]_\243?\277\315\244\241\324y\275?\244\366\376\215\236@\301\277%\024q\347\000\370~?\302W\214\031\265k\223\277\306\224\367\346q2\307?\270\323s\376\002^\273?i?\333-V\226\257\277\361\323\017\0372\324\256\277y\225\177\221Pn\217\277\364\277\211)\005\205\227?\2427\023\362+\344\320?\030\241\362\342\345\367\266?6\277o\312ct\225\277\315\274U&\344X\233\277\366\236L\235\252\366\232\277\205\257jG\000c\301?\254!\321T\335K\210?\271\256m\231\014#\251?\3074\247vbp\302\277\257\204\221Bq\205\276?\214\211\325\330w\204{?\201\010zE\340\262\320\277\354\243j\301+\014\233?\2407\306\2252\330m?\tp\006Q\262\361\276?\002\360\330\035TK\315?{9Zq\313A\252\277\341\312\370.;\314\264?!f^w\2274\272\277\214I\"\025\020\220\260?3\363\305\211\'\257\230\277k\337E\204\257[\207?\213\272W\222\202\345\302\277\212=\265X\201\010\270?\255\244\311f\030h\225\277I\363fP#G\303?qm\"o\'8\261\277x\266\222\251\206g\264?\275Ol\234x\253e?\345\262\377w\'5\310\277\324\331\223J\221_\256?N-\205]F|\277\277\207\341=~q\224\276\277\277\033\023W\341y\225?\317\353\312\330\344*v?i*H\277P\310x\277\337\264<\367\177U\217?x\213\350\020uw\266\277\230D\032i\375w\261\277\235k\006\027\266\252\257?\230\246\33721\303\254\277j<\366\226\314\352\241\277\234Z*\350E\337\266\277j$\0059\377C\306?\364\237L*\016\033y\277\005vU4q\\\205\277Gq\201%Y\303\276\277\215\207\003`\261\204\237\277O\r\376\270\r\267\263?P+\234\037\004\237\275\277\302\354\232\236\035\036\203\277\262\243\247\026\3239\265\2772\275_au\304\276?.\236\336\2515\326\235\277\310\036lu\024C\251\277\325\270\375\372\357.p?\204\004\277\262\034\237u?\252\035\271\202\251\240\262?\262\024\3522\000\332\306\277H\302-_\276\026\262\277,\374\r)6\217\265?\230>)\305\365&\270\277\262\222\323\330\247\321\237\277H:=\277\370\320\265\277\312\214{\037eV\241?o\326$\223\362>h\277\260\nQ\232:\360\272?z\337\315\306\277\356\272\277}X\317\325\013\357\245?|\233m\205[\244\304?\250\374\375\350\270\217\263\277\3513a$\207>\301\277g\"\325\347>@\267\277\201j 7X\004\261\277\2526\210c\3469x?H\313\314\032d\322\177\277fQ;\004\251y\242\277\312\312\031A\177\266\243\277\372\304>\026\366,\270?\341\006%j\337\317\300?U\240*ZDX\274\277\003r\247\315\321\356\323?\362}\344\341\"\013\241?\330\320Y>\000@\261\277vnr\307\375\333\301\277\263\376\257w\367\334f\277rdz\375\336\013\244\277\372\230M&\032\277\265?q\313GZ\025\346\305?:\270x\035\344\202\223\277\334\177F\325\226g\252\277\324\006\030f\343/\261?\345\035\204\037\302+\314?\242\247*\271\256c\262?\n\365\025^6\306\263?I\214\366S\002\366\307?QB\213\003d\216\267?\r\250:\232\373G\244\277#3`\341\276`\306\277Q\020\367\260\264\037\263\277JhO\310\351\r\242?\256e\263f\207\345\264?q[\370H\214\252\313\2772gey\003\014\305\277\302=HC\030=\261\277\007\026Q\374\250\211\254\277\322oW\356\207v\301?P\277:\210\3568\203\277\364\362\214\234\302A\216\277\033\320-Q\344\276v\277\257\277T\262\344a`\277\230\341U\006\002\206\264\277\222\257\263\n/\224\261\277\222j\352\335\306\257\306\277J\340t\034\356\315\277?\362\207\215\332\007_\271?\224\272\3365a.\272?\216\324\215\330I\361\243?p\330\316Ta\236[\2776)f\014\215a\250?\343J\253\025b\324\245\277\217\243~\316#\375\244?\323I\206\345\321>\226\277\013\265f\304(Q\302\277\351\261\241!\255Q\263\2770\201w>\344\363\274?L\270\374\276\313&\300?\021\217eHq\213\267\277\333\267\332\340\214\353\211\277\261\3560\023=A\254\277\203\n\206\277\326\341\240\277\206\235\n\227j\342\264?\223\342\234\364\335\304\267\277\035\314\021U\253\324\221\277m\312R3z\243\265?m\250\207\202\224\252\276?\232\356\014\237\336Q\231?\004WBE\261\023\264\277\005\315K1M)\232\277O\002\344\335\302\025\264?_\270B\343\327\217\267\277\212!\316-+/\316\277{\201$\243s\344\252?\375\317\014\245}\023\271\277\353\355\232t2\264\303?\002~\017\341A\215\264?\237n\343\3575\314\240?~t<\000/C\267\277\210\246\301\247\223\363\301?\375\2648hP`\213?\357\245\314ulh\270\277\327c\340\322W\"\274\277\227@\010\321\211\001\304?F\346\205\n\301\255\241?\223)\023\262\205\'\226?_|\253\010\245\007\311\277\004\242!\277y\022\254?\201\357\261\322hC\266?\374\010\234\346j\033\260?\004\277\0070\2462\240\2775[g5~b\252?~\357\003Cq1\274\277\353xH0R\000\301\277\311\212\210\353\014|\270?\177f\013\366\322\336}?\206\216\223e\253\327u?I\354\036\255\2610\236?|\340\273\327(G\225\277\n\225\232\2750\217\303\2778F\024\302\2546\320?\226>\315\260\210\347\255\277\037\224\213(_\030\224?\246\365\331\236\327\340\216\277\003P8\313\215B\307?\320n$\"z\202\252\277\236\244_L_\n\252\277\020\352\032\231\227\003\307\277d\344F\010m7\265?\331\335,\262m\375\275\277\246\261\362\362^-\321?}\337\237.\2245\260\277xl\024\217\023\237\256?F\213\252\026\021\274\310\277\241D\364\320\372\210~\277\025}\270\337\251\244\206?\264P\364\221\317\021\302?\270\341\3341\250\266\252?=\026g\246]4\241\277\336\325\"?\213D\202\277\345\331\022\342V\212\254?g\315\020\301~\344\277\277m\240$N\371\021\262? >\210\322\007\233\260\277\305!4<\374y\265\277\342m\305\310\377A\272?\322\233D1\030\206\232\277\220Tf\376R\247\261??)\243.m\035\302?\201p<\317s\363\264?\221=\211Ns\252\301?\022\242\325\201x\201\301\277\235#\332\230\324C\266\277uuv)\030\212\277?\271+u\031\231\r\265?\275\236!\022\334\034\304?v\274vg\275y\300?~\254h\027R\221\214\277*\"\000i\201\005\244?_;y\271\226\345\231\277\030\300\225){\252\277?vJ&\277\021\312\301\277\265\355Hp?\236\320\277h+p\304\210\351\177?q\237\363o\234z\264\277$\226\331\252b\035\263\277D\363\237\313\333P\244?\342O\037\213\217_\262\277\274\251\343K?\313\275\277l\2527.\255\356\310?\305\256\017\305\033\240\236\277\276\'\"\357/\310\262?\003\326\034R\352\306\221\277\375\205\016X\002\017\245\277M\004px\372\327\316?\275\3713HB\243\273\277\021K\000\232I\254\276\277\277\2377\374\272<\224\277\2706\362\230,\335\245?[n\314TP\240\315\277\034y\273\205\025\247\266\277\025.\323\307\000\017\234\277\313\033\243\010\013p\250?\303K\353FK0\227?n\3021\322\354\237\236\2776\275\266uF\017\250\277\373\331_\356\037\217\257\277\245\237lr\276\177\271?\344\013\321\004{\037\300\277x\002\206\344\203\365\206\277L`\211~7\241\315?\"\235\344\032\371\247{?\332\344\261\001\373\034\230?4&\025\374\303\277\250?\307\333U\220\272\261\241?\216\205\343\204}bz\277wngOp\351\237\277F\257a\251\306\242\300?/\267\025,\216\204\302?\302\332\214\023\364\364\272\277\365\226\256\307\273\324\314?\371.$\210Z\265\243?\241\235\030\200\313q\272\277\256\271&Y\366h\272\277\014\322\250+\270\367\302?\313\032c\035F\177\255?i6\\O[\342\260\277\3058_\006\367\\\262?\217S\007\315\217\306\261?\314\223\203X\370|\251\2772\037\217\214\2278\224? \346\216.\031\'\314?7\341\362\333g\213f?|\r\306\355P\022\226?\207\356B\365_\356\264?W\323\353\336^\335\301?\2159$\315\223\207\301\277\355P\243\203\264x\276\277NC\277\322qH\216\277]\212\246\324\235\001\264?\216(\337\221\004o\266?\275\242\231\254^\224\277\277}i\203\366\357\030[?6\334G\325S \241\277/|\251\372\200\030\273\277\261\265\032Zn`\323?i\034\020\036\344\277\247\277\361\031\376\275\t\330\246\277N\3519W\270i\305\277~>9\207#\223\236?s\013\3559\345\220\222?\2708\203\031+\353\266\277\024\330\252\204w\214\236?`z\371\031#\211\214?3\372\215\306sf4?\237\214R5\021\266\301\277\263\017_L\320\220\300\277\362\330\267\327d\330\231?$\343\025\365\206\270\236?\236p[\221a\362\243?\tfo{\225\032\254?\365q\n\2401\244\251?\345\375lQj\317\302?\344A&\306\3112v\277\035\330\307 u8\240\277\314\250\331]\316>\276\277\333\257\021P\364\244\225?\244`\374\225*\241~\277R\235\032\235F\325\220\277|q\2726\362\236\227\277y7\034u}\256\251\277\177\000\367I\310\355\221?\255\337\240\336\240\216\261?Qn\224<\223\230\254?\240\325\177\254\241\371\233\277\357 s\326\251i\272?\001\307\201\331\376\275\321\277\251;\275Z\345|\261\277v\277\306\207\025\245\302?L\024\366\202\243O\255\277\203EQ\336\227\225\202\277RC~#\022-\264?@\233\277d\027Q\261?\3336\010\201\270\021\217?K=?\256\024\032\270?\331;\301Xy%\224\277\203jf\250C\271\270\277\0333\341O\327\265\256\277\254\201\266G\001u{?\234\341\325\272\223\230\203?2S\243\033\254u\275?T_z\302\311\212\263\277zH,Z\336\363\303\277cH\314\374\253\353\255?\304\306\242\343\350\027\340?\001\363nD\270f\252\277Vm\335\355\267\262\265\277\256\n\035\256Z\007\265\277Gu\3059\323f\307\277\207w[\273\313\"\246\277V\000\267\317\253\007\226?V\033\257O\324\272\272\277\331\265\357\327\351\241\227\277\004Y\305\217*\340\201?C[\033\321%n\266\277g\331g\330y\316\221\277\265\254\374\207\177D\230?n\020\254\351\254\036\225\277c9\025\262\233\330\274?\316\354\2707S\216r\277u\336\354{\367\217l?\303\270\007Y\233\270\263?<8\224\367Q\024\264\277\222c\204\242 .\213\277j\234T\001\3061\300?vb}\264kA\270?\033o\245\314\246Z\247?\320P6\334U7\303?\207z\231\335\271\032\237\277(\007b\317\364o\201\277\301\372pJ0\205\277\277\254\201=\225x\232\321\277\314\213\344\342\226\"\302\277i\337\343\265\235\001\242\277\007\030\221#3\200\247?\007\370\361\216\220?\321?X=R&\0324\261?-\220\272Rp\363\215\277]\205$\024\002O\271?8\005\024bb\007\255\277\210\257O_\351\335\255\277\353l\357NWp\241?vBvO\367\023\317\2773\030zt\234\254\223\277\322\203\302\213\330`\277\277\215\211*:q.\320?\303s\n\006\034\231\250?\375\017\204\275U\315\241?O\224\240\035\334\357\314\277g\233A\r\357\305\222?\272%\371\212\214\001\325?\037\364T\311\nr\261?\004\206A\354~i\226\277\352\362\"w\235e\305\277UC<B\031%\261?Ks\224\2244\270\225\277\014\300\236\330\265\241\265\277s\267t\375\240j\272\277V<\377\210\230^\306?\226\203\022\005R\335\273\277\264\331\035\310aX\276?nc52\360;\250\277\017U\3721\230\217Q?\203@\213\311\\;\244\277\317\252\232\377$k\255\277\2531(g\265n\273?\254*\336\000\244p\276\277\207\245s\235\274\224\231\277\010\225VW\332\270\220?\362\212n*Q\370\246\277f\321\253\267\321\265\266?\373?\207\240\347*\200?tUA\202B\247\265?\300A\317+\251i\265?\344v\022\3746m\313\277\247u!%\250\034\262?\264JDt\035\271\241\277\205\t/\177;\270\245?D5\263\202\352\332\246?\264\267\024\205\034\347\231?\315\001\242\270\031\224\254\277\201\227\242\302\234\227\324?\025@\337^\004v\303\277\2638W\024\2137\263?\262uE+\314\240\264\277v\226\350\313\010O\236\2772\343\310\224\t|\231?\217\377\207\203\325X\227?\202\2151\333\265\223\302\277\310\022t\356\232\310\271\277\376;\001~I\021\237\277T\212\231\033\r\377\257?\301V\364\220j,\303\277\'\275\346\274\236\323N\277y]\210\220\304\360\263\277p\344\302\na\236\215\277\202:P\376\357\312e?\212E8\035n\001\252?q\3207\255\355\225\313?pB\342\006\242\273\252\277\320\341c\207a\n\266\277Q\240c\377\257\035\273?4\316\310\250O\335\323\277\231\336\200`\236A\240?\237\244\264\302u\270\220?G\202\330f\324\214\303?B\304om\002\022\247?\224\220g\307\313\255\250?\311\n\365NVqq??\311 \324\366\237\320\277\177,\333.w\355\262\277\3210S\261\002=\251?\244\212H\211\350Z\322\277l\343E\033\232\247j?\206m0{\324]\215\277\\}\255\235y\353\301\277\372\242?\325 \311\327?\333\342_\021\340\326\261?\257u!\024o\320\250?\351\037\2378<\002\253?\323\036\246N8\346\301?f\212\220\227%,\253\277F\202q\003\313\252\263?0\302S\215\024\024\301?hk\003\006W\320\250\277P\227\225\307\205\232\300\277\262\021\345J\362\261\232\277\361@\306\207\205=\273?\302\217\036D\020X\207\277l}E1\2427\235\277U\334\t\322Z_\263?+\021^\270\217\330\256?\335\270\362\317\276e\257?\2313u\343\363v\271\277\226\026\025H\360\030\225?\350d\375Z\010\220\260\277vS\247={<\300?\355s\343\2111\236\321?\2374\304\251\024I>?D\272\377:0\243\263?Q\023\215)\242\210\251\277q\350\300;\374\240\275?\036\272\305\'Jm\312\277\264\315\327\035\013\300\271?/\306\341\335\230&\301\277\000G\023n\007\224\217\277\205\257g\302J\265\216\277\323\372\202\')i\254?\237\021K\322\\\001\255?\375CX\341\205z\243\277\243\304\373\215\351\304\300\277\310E\240\005\247.\313?\304\334]\250\317\313\225?t\025\341<\377\235\241\277WSf\221u\274\300\277=n\205\343\330\236\264\277\027\374\343\241\267\235\252?w\316x\023o\305\241\277\237\241Z|\256\201\316?\201\026\263\007\316\235\246\277\250J[\252_\227\231?\306\253\213+\272\034w\277C\264\314\336\353\275\273\277\344M?[\214i\252\277l\003s\273$\321\256\277\361sJ~\t\337\270\277#\335\220\224\371\n\221\277\307\177\037\323Fo\213?\342\003-c\026i\331?\307\272.\031\327\340\261\277N\211\224\355\ta\300?\304\215\346U\360\210\274\277\363F\330\020u\005\326\277g\212;\\M\010\213?\027\177\000b\243\025\236\277\261\024\321J\260\211\265\277f>\243\253\217.\240\277F\235\003\0027\340\260\277^?O\316\226\316\271?\0354\2271\2661\305?B\264\001[\203\005\021?y=\366\255\373\316\250\277\311\203\252\030~q\260\277\350\206\265\271\231\000\252\277\360/\007\330\362\347\245?\244\263cR5\n\242\277)\226V\002>\346\224\277\037g\237\345\034\202\265?\021o\240\021\264p\260?\214@\014d\r\032\212?\243\371\325\177\307S\276?\304\2346B2\007\220?&\215\022\305&\326\307\277{\362\204\336\325\240\212?z\300\305\272\246\005\262\277\350\345\017N\n\004\266\277\030\200\363\335g\003\310??\354\007\311r\032\245\277=\350:k\255\210\264?\353\026\366\232\323\256\273\277\036\251!\014i$\217?-\267\373F\3268\223\277\212\331V\247\001\364\267\277TR\000!\000z\217\277K\201\024z\177\023\220?\361F\336\332\350\233\237?~Y\332\r\222\023\216?\020\262c\342\025J\272\277.\231\236\300\353K\266\277\013\276\223\353`\322\241?d\001%,Tl\275?\232\336[?\230/\246\277\252\313\020]\342\270\251?\032\213w\225\342I\276\277\331\222?\036\246eu?\350umGr\252\203?\365\375<\245\246\344\254\277\200\227\202\240\207B\212?\330\013\333P\235\257\261?\246\007\342\013\254\264\241\277C=y9\331o\223\2779\240<\307\317\311\301?F\315x\002\366c\177\277\252\370\207sF\334\277\277f6\207\261\3175\251?\001$\001\330W\227\241?6\323\014\237K6\260?j\\\231\367\025B\245\277_\355\016\353\216\270\330\277*\270\240\303U\030p\2777F\025\2744r\266?uT\2114)(\274?\372\264\230\217f\230\253?\301\007\237\255\207\352\270?<f\336\224)>\275\277\251\322I\034\334\305\252?c\n\032\232\327\326\300\277~\224\357\330 \313\251\277\274\340\276\304\333\007\277?c\277\343\332\214\327\270?\215\000\352\312\211\353q\277\206\347\007!\327\243\323?\322\215\267\326\t\363\263?\017;@\372\223\332\266?\353\317\017!\231\361\252\277\242\207z\001\270\235\260?`0\205\022\250L\240\277\030S\010\234\315\206\272?\016-#\243\224\030\270?\204\301B\361\242\316\216?\315\310\242%\'\004\244?\006%O\3125\351\256?\014?@\375Y\325\276\277\215Jb\206\264N\261\277\271\341\244\006\271\232\241\277\204W\205\237\021d\257\277j\321_\n\214\321\273\277\376\204\342\272@ \257?\376\363\376x\254\302\232\277\324\214\322p\350\312\262\277&\205\036\260\220\207\254?\037D\375\r\010\310\263\277\241\350\023\024\363y\232\277\206\314]T=\337\267\277z\257\227\322\017\347z?\212\314m\236\355\367\276\277D\310z\334\000\002\243\277\350j\306Y\"t\262?\211\235KL)\325\273?\233\245}\213N\346\301\277(\262\221N\031\346\266\277\003\217\200\317\312\023\304\277T\341\324\201$\022\265\277\245Rms\033\363\260?\256\214O`\013M\211\277\270\177r\r\226\"\302?\265\225\255O\270\004\265?\305X\360\311\205\024\271\277\273\227\tc\300\356\236?\253H\242R\t_\300?c\333\362\334\030\235\265?\205\274\303\2531{\262?\366[\306\032\240\222\303\277\374\332T\321\301m\261\277\304\321\223\327\240\r\323?T;\222\352%\217\245\277\275z\212B\223ww\277\003\311\214\224\273>\206\277O\'\017\357M\303\254?\267\267\301\001P\277\217\277&\313\216\332\323\260\267\277Z\361g\3303\030\273?\312\266\305F\304H\260\277\002\343\217\203\022\350e\2777\025\250\210\376\266\275\277Fp\326D+6\220?\222\374X\026\005Qx?{\362\302\3453z\231\277%=\r\311fd\271\277a \332\327\004\016p\277\035\3528(N\207\256?\226\'\276\352z\310\240\2776\321\004\357\356\200\301?.\213\263\344HY\244?y$*\031Y5\233?p\310~\250\303\330\254\277\357\303[\327YEx\277\331\313\023\357\227\030\251?\033\341\200\303-\026\326?_\223\211\246\221\267\247?\211\322[\352\257\271\227\277\375y\343GE\031\260\277\027u(\247\255\363\325\277\246\245\225\0303\271\253?L\256P\364\233:\303\277\201J\020\304|\021\304?\235A\276\3636\035\236?hvc\324<\337\271\277Po-\250g\320\260\277\223\363\260\363\307\363\327\277hD\377[\317\236\242\277\355\354G2\237\365\256?\257>\337#\344\233\320?\265\320`>k[\233?\031p}\325H\334\260?\230\336H\263^@\300\277?U-3r\364\211?o\007`\210\010\226\204\277\240Q\216\347<\'\250\27731OE\266O\307?mY\353\217\276\244\222\277\254\273\314o\032\276\251\277\364js5\324\t\234?a\024.\203\024=\322?-\217\034\237\005\006\241?\322\035\2363B\352\255\277\254=3\327\232\342\253\277\355\226\320t\000\036\242?\246;M\362\356,\226?rA\020\026d\347\260?\3716\026\362\326\277\201?\275\023\026\356\364\335\244?\002VOzk\333\264?:\273\240#\270B\222\277o\026\014\302\271\364\305\277\"\373\rM\352\212\236?\304bj\360V\261\306\277Gw\273\274q$\262\277c\236\365\367E\200\270?,\023\216\312\021\006\213\277P\026C\364_\212\313?\237\236\350\311BZ\263?\006t4\036\357k\225?u\205e4\345\233\271?G\227\023~\203\003\322\277\373/^Lc\n\310\277 \270v\213\276s\263\277\272.ww\253\356\260?e\312Ge\265\220\225?\362e\005\245\367\333\242\277\345\36642\201\006\272\277\033v\216\207\341j\246?\036\361\330\275\230\233\242?\341\376\237k\317*\301\277WGT\006[\372\302\277\256zIL\215\315\225\277\257\002\241q\272x\256?Y\245{\314\007\330\302\277\320\3409\335\346\332\314?V!\320\331>+\252?|\303B\334R!\271?\025\224\301\376\2142\270\277m\252\253.0C\304\277\342\203D`?\322\217?\274\362s\344\231\247\246?;\223\203|\347*\302\277\3144;\310\032ek\2770\300tI8y\276?\336Q\316AC\232\267\2770\341\267\301\033\020u\277#\370\3630\257\345<?\336\023@\311\263\352\305?\343\010M\303\273M\251\277\010\374[\235T\373\240\277;\376\034&d\331\213\277\226;\321\320\377\317\277?O\343r\233\204\273\257?|\326j\236\213\000\235?\243\262\250\006\332_\250\277]r\024\256!h\235?\007\2223\371\2030\231\277\022\334\226\304\3311\230?\272\262lg<\265\263\277\0373I\310\213U\250\277<\332\376\34569\311?U\205\225\326\363\257\266\277\035\367\304\006\t\360\236?\0334{b\334\361\240\277A\351f\243\340T\253\277\241v\362\272\325-\224?u[y\222.\"\267\277\021O\343\035<\310\260?\031\3532\272\257\330\225\277\005\240r\336\203\200\300\277\376\216\231\337\0277\252\277\033\247\306\334\177\221\265\277D\007\355\215\030U\271\277(\005\264\353\330\215\267\277\0249\t\243\240\246U\277\261\353\273@\230\214\262?o\354\024\315\257\363\306?\350\376V\t3q\276?\237Y\027\264<d\240?\221\302\214>\374\006\306?\306\267\231Z\264\241\231\277Z\274\212\327\'\212\241?\264\213\375V$\027\263?P.\273\253\032b\305?G1\010\033\016m\241\277\265\362vE\230u\266\27789\372\257\315\374\237\277f\2406\314=\273\257?\246\221\037\362\337:\266?W\361\305\304)D\226\277\231\354\023\2208\225\263?\242\334\246\255{\314\245\277hH\327\227\210F\257\277dMc\244\314\254\302\277\221\337\324FT\264\313\277e\301\312_8G\232\277\307\367\002\343\311\270;?\022\001\001ZXA\314?\0108\231\037f0\224\277\315\030f\023\361$\244\277\203%\033\315~\301\307?iJ\236WW\303\302?\022d\355L\033\252\314\277n\037\314k[\212\303?(\205\271\013\321\255\312?\250j\370asO\263\277\2141^w\022g\300\277\031gl\357\307\375\264?a\350\201\303R\335|\277\357t\327\3543\026\260?l\361\005_~a\311?\246\3054\305|&\255?\317V\355\235\316\212\263\277\0369\356\002\365d\241?l[\371\357\301\214\241?]\177E4\241\253\221\277{\203\233~\316c\246?\036a\"kX\315\244\277n\316\317\314\355\257\275\277\347A\032\016\316\024\300\277F\325\244\330\025\305\264\277\343\333\032\030u\315\240\277\027\366\207\025\3344\272?\356\214\205\361r\177\220\277m\372\363`>\226\235\277$1\'[X\300\301\277\356\002\366V\242\377\267\277\320\'\023\250\232\206\247?\232\216/\2729\034\213?\371_u\314n\215}?\3501\264\327\200\231\311?+\006\263\3561\010\271\277=EIie\234\267?3\251\234\250\333m\227?\261\351E\353\3613\302\277\222S\375\213\335R\245?\220\304\220\211\243\033\241\277\346\375\312\366\001\307\222\277\222\270\211\rsR2\277\360\004!%\222\360\265?\014M\314a\251\n\260\277\214i\234\322\311\207\303\277\032\353F\211L\317\212?\017\375I\326s\246\255\277\034\025\226\2443\325\313?!b\303\016\304\230\237?\362\341\346\206C\204\247\277\350\373\336\177Hum?\334\200MP{V\240\277\024p7A\036\304\305\277\373\277\251L{\275\250\277R\317+fViu?\n\302\254\356\025N\300?\220rg\361\226\306\303?\245Z\034\324 \250\242\277\025rF`\241\256\220?\007|\005\356\271\360\264\277^\271\363\005\354\331\260?\312\346\321#&\234\254?U\t\336~\014\260\220?.\025\210\227\"\035\222?\023\317\361\326_\240\246\277,\3452bSI\260?\002\270\227\210\274\276\272\277\247<\367\222\241C\236\277\265J\215\201\233=p\277\347\273\202:\321\000\266\277\233\n\223\317\216w\272?\374\272g\271\315\177\212?!\206\271.\312\323\226?\304\000\303It;\252\277k\330\033\034\361\334\244?\242\005y\263\220P\262?\244g\234\342?\376\306\277l$.\001\247\326\212\277f=\341_\016s\263\277\224V\236z\224\323\257?\032h\0055\243t\302\277\301\263\204D\376C\263\277\026\362\035t\002\234\235\277\235\n\330\212\004|\266?\352\311\314\333\324\346\242\277\360\2046rQ\261\225\277\362/\243\177\221\273\242?c5\256i\352\230\237?\357|\344^m\372\246\277|k\341K\251\033\247\277nG\275\322\273\022\267?D\251\030%B\264\271\277\345*\3079[d\246\277\202\311,\350\264\177\246\277\232E\337bgP\275\277\321\254(\340g\024\236?\201\321\254\313\007+\241?\226}Q\246\315\272\240\277gL\005\344]Z\245\2770o\366x\265\034\251\277Y\\\246wF\303\240\277Yq\r\236\262t\320?\313\356,\020E\006\253?mq\363\207=U\266\277u9\311oM\374\243?\272\2349\006\310\300\234\277e\361\005\300\324`\264\277\264\313vR\017\024\253\277\335c\2235o\t\277\277\224\355\274\005\023\003\301\277\010\265LF\336\336\304\277G\323+\202v@\243\277\230\362+u\202#\310?^\235f6)\177|?\351\210\250\'\370\213Z?\016\006\375)\361\177\314\277\347\023\2277\217\257\221\277\213\002bY\222\201\302\277\362\212\313\375\220\267\324\277&\357T\007D\036\333?\343\262\351;\370A\237?V\252\207\035\210\034\240\277\251\314\253\275P)\324?\2413A\005\370l\306?P\222gU\350,\267?l\217\016\204~s\270?hh\023\252\274\227\312\277\010\311\336\264\333\262\262?>\306\202\224D\230\262?g\017\2477L\235\311\277W\223\245`V\021\215?\232\251X\304\211\302\260?p\204\233\362\334\\\256\277\252\256\260\377\301\036\275?\020\357\371v\236c\264\277\345\313\353\202~\236\247?\000a\217\370\213[\241\277K\014\236\241\364|\261\277\356bCf\214\035\240\277P\262\354\201\331\343\262?pdI\206\304\301\245\277)\357@\256\214\320\255?\212}y\017\035=\232\277fc\305j\314P\225?\211\017zGr\316\261\277\346`\005\300A\365\221?\234P\023\246W3\265?\356qW\354\263\024\333\277\230\374_\'\r\233\246\2773Vf6\022\307\310?\312\312\360\303\017\204\265?0\230\000\377\314\r\217?\236#\322\313\201\312\233\277\364\037{tal\303?\260\r\211m)L\216\277\253\t\033\256\356m\303\277\337_\332\230\257\006\267?\311\223\277,\364\344\266\277\207\273\234R\014\312\230\277}\345\022)\331&\200?\017\0105\330q\247\311\277\322bR\257\266\235\310?\336m\234\274\245\257\225\277\322\303v20\036\311\277u\260mV8Dh\277\263\210\344\260\3474\277?]\355\360\275\300Q\222\277{\205\305\032\033\274\272\277\364J\2455s\345\276\277\312\240\270l\267a\250?\2436-a\360M\301?\225\022\334X\0339\305?\324\276Y\300\007\030\263?\270\326i\370yh\305\277\347\210\342\220/0\244?\274\025\332\224\307N\300?iC\2661\002Y\265?\244\350\263\264\244e\303?\037Vk\373,\257\235\277\355\255 \375#|\274?a\373-\261\252Y\213?x:\\\004\251\030\242?Nj\2018\325\364\250\277\030\374\200\236F\230t?\235\272\334\0042Z\204?\037T$\365\310\340\310?\003z\220?p\316\252\277\2755=\304\352T\314?\320\266\222\370\246\204\216\277&\016`\367yx\303\277\351x\003\267\222\264q?\226G\325E\213C\237?gm $\252\376\252\277\211\376\361\377?\273\306\277\211\014*\026Q\246p\277\221\01495+w\246?\3601\337\275h\266\220\277\014\300\"\035\214\256\225\277A7!\020g6\201?\324z6\342\336r\317?1OiaZ1\265?@\014\345\252y\007\261\277X\360/\233p\362\300\277\365\033\303]\266>\326\277\207\356@\010\372\207\216?\234\033\260u\366\360\262?8\327\32706\256\263\277\330\237?\333\t~\305?\255)\034\342\316\223\201?%\027\367K\351\001\257?@b\266K-!\232\277\346\305\262\257`\232\260?\376\364)\277.\326\271?9Nu\"\247\333\220?\035\"9\003\257\345\255\277\220\205\217\345\230\320\260\2772\351\273\234\222\204\277?\256B?7X\271\253\277 {\210\225\207\225\266?=\357\3167\204\037\242?\244\266El\205\351\240\277\037\370\260\\\317\312\226?D\320a^Tr\206?\027nw\034\004}\242?\355\305\236\245}\362\315?\325\305\270d\216\242\210\277\004\317Gi\3559\243?c\222\001\254=\216\313?\033\353z\325\001\032\205?g\024dN\347\323\267\277\032\277\235~\005\264\251\277\373\212\340\207\244\275\257?\211\232\224\264\240<\222\277\034\350\255\3764\260\234?7]2\217\347\270\243\2773\023,@\243\033\266\277\225\205\t\242\324n\223?sO\347\324\350\372\305\277\364\302\020Qn\241\214?\277\321L\365/&\264\277}\301j-\020\342\210\277\302\315;b\r\356\273?\316\345K\2269#x?\031\306C\\\372c\302\277\373f\225\234v\302\305?o\355v\237\321`\276?a\261\243\333\300\221v\277\'\241\264\376A\315\244?\231\327(Z\311R\272? \210\010\315;^\241?|\212X.\230\\\260?\'\207Q\363\212I\231?\303\016\315\013\220L\303?\034\345\376:\206\nq\277\206>\246\315\r\004\206?\261\361\302\177\306f\272\277\327\324Cc\001\315\250\277p\343\317*\'\221\221?;U2\001\265\277\305\277^\250\226\372\316\020$\277\352\235X\247x\313\254?\363\355tA(Y\300?\003\333go\316\375\275\277\347\352\277O\367a{\277N-Ri\327\247\222?Kj\2279\324\231\234?>\351@\354\3277\230\277[\267\2050\234T\243\277\2656\322\222n\005p\277)\205\'\340\263\n\274\277\215\344\371\251]w\270?\000KfF\304V\241\277%x\271\224\251\354\261\277\277*_*\375.\275\277\004\320\351\004T\234\233?\207\256\2423\317\243\273?65\'\216Q&\224?\001\372\377}\340\036\274?\362\022\031R\016s\205?\006u\374\023XZ\244?\032}\214\226\347\251\255\277SN\223\264\r\206\251?\320U\241\036R\304\300\277\310X\260\203\'\201\327\277\220RR\216\256\306\214?J2\373\2116\033\276?\277\274E\205~\213\256\277\277\237\001\234\357\303\304?\t\3072\340\371\"\272?n\233\020K\217\232\250\277\346\227\203\to\033\272\277\353\224\030):o\262?n\rT\227\215\315\243?!R\371no{\225\277\260U\311\253\336\244\276\277f\375mI\365\252\242?X\371|\216\221{\276\277&\234\222\250\324\312\320?#~\2326\211\241\227?\336\336\343\324J\373\272?\007\323\256N^\360\210?\353\274\314o\271\347\216\277\2646\370\211\020y\251?jY\177\365T\305\270?^\243\206\247\300\375\232?U@\010\311\236\227\200\277\016RW\202\275\322\240?\227/\037-@\037\267?k;\315\212\312Z\243\277\325\356\204\245N~\205?\341\326\0334\272w\225\277\356\230U\210\306\305\312\277\033|oF\304\375\227\277\025\367s\230S\233\271?IU\276d#\243\246\2776\354go=\276\243\277]\230i\370\376\206\262\277h\013&\345[\024\273\277\344\037\315o\301\355o\277\320#vZ\007U\226\277#\005\372\330\316\264\220?\345hk\035uZ\270?\005?m\341@\273\300\277\370\267\337\277\037q\261\277b\017Q,\033P\277?\356S@\364 2\250\277]{.yk9\262\277\375p\275%\034\363\267?%\216,6\256v\306\277\r\227\240\215&R\303?\200B\353\304\322\361\261\277e\331\323\361\002g\260\277K#S\251\376\360\241\277\371\331\356\215\267\252\221\277\031\313q\264\314Tp?1m\316z%\032\261\2773\016\263)(n\300\277\005\'\362\2432\334\227?\363\n\330\245p\344Z?YN\033*Z\322\241?\321\267%\302\341#\257?Yz5\037A$\303\277\357\221\353\337s\207\267?u\245\030@\361H\303?\026\375??Q\231\212\277h\267N\031\246\t\276?\241B\024c\326\225\307?\326\n\355I\274\332y?\247\0376\253\224\035\267?I\255\347_;`\270\277\276\226g\373\334=\237\277\332\274.|\243e\243\277t\376z\355u\361\242\277eT\254\244r\207\307?h\370>[G\255\202?zG\356yk\317\303?\314\266\351\232\206\223\312\277\217\344\242g\303\377\305?G\244yBk\356\246\277\0039KM\306\311\270?\237u\211\324a\265\244?\226s\342\375 \272\231?\356X\017a\377\203\235\277\263\2374TCI\272?\374(|\335\327Z\300\277\332m\376]\000\017\206\277`1\3731\023\r\267\277\204op;j\200\266\277\236\224\327/]\035\271\277\004\336\365\223f\213\264?\025#\262<\210-\262\277K@\207U?\220\260?\336\227#~\216\207\261?H\232r\271hS\260\277K\376\n5\254\335\251\277\364\354}i\022\303\243\277\324\0170!\260H\200\277\310M:\237`\315\261?\305\305\340y9\005\271\277NB:Q\270\274\241\277HP\243\214\247]\254\277\247d\333\322c\023\300\277YD\0362\272\205\267?&6\'q\034\352\262?\020\210\374\261Eg\250?;\003\2555p\006\300\277\275C\364\344\032\344\230\277\367\014\014\017\273\035\227\277\370Y\020A*\226\251?;\177s9\326\353\245?A\3144\200ykd\277\3663\312\022\017\006\250?L\261\274j\3465\300?\334j\372g\3150\202\277\037\020N\222\300\326\203\277\245\336\335\270QV\221\277\344\235:\021\376\331\241\277\006}B&x\362\207\277\371x\230O\025\343\306?\223k\207]l\316\302\277\323{Qr\265N\237?\007\306\377/\333)\235\277b(\226\020\035h\271?\217\033!/\322d\310\2770\177\032\267\257q\264?\316\373Ez\014\320\243?0\322\300O\241:\310?\2726.Kw\354\215\277\021\017o\204\364-\177?\244V\202\377P6\313\277\355\256c\2611Lp\277L\001\025\021J)\265\277\275L\272N\272\205\262?v\325r&h\246\266\277\311\346\234F\364\202\237\277=\253W\221\325\034\250?\352\2070\323\363\320\261\277\314\246=\r\017/\302\277M6L\307\\\240\272?\277\273\311\354q\014\250\277\353\310x\273\332*b\277\0160y\213\203q\252?\241\265>\031\216\217\264\277\257y\344\034\271\275\330?\025\210\\f\3413\222\277\374\206\204\342\331\306\202?\035g\215\351\374q\310?Z\227\241<(D\322\277N.\317`\277\255}\277\246*\033\021H\376\246?\270\223\030\'\201\330\305\277nA\326\002/\201\243?B\232s=\014\375\242?Xr\204Z\201\335\266?$\240\210n\257\216\304?\264\006\300i\033\206\264?\302\322\254T\207k\234?#\267\025U_\227\261\277{\247\335\273rp\260\277\223@%8\224<\312?\232n\332H\321\321\266\277\367\311\3037x-\322\277\215\027\353\220E\'\253?\332|I\210>\216\225\277{\322\006\213\352:\263?j\225\031\261\221\231\037\277\277\240\357\335\237\344\252?e\002\315\3652\'\271?\tt\306\256p(\307\277\364\323-\203\340m\230\277Q\376\031-\004\300n?\003\317ie#f\270\277\023\032\361\330\375\200\235?8\247\335\257{\372\244?S\003\\\230\267\336\241?\035D)2\275\373\277?Q\346\243\217\370\243\270\277\r\274u\243\246\364\233\277\036 \025\317U\034{?\275?\'sF\260\302?dM\323\200\017\226\220\277TzpF\364P4?\372\354#\216\213c\323\2771\234\316\216Mi\271\277_\271\234\031\262\263\227\277\365s;\035#b\301?\014\2254\375\272\350\211?\332\325\026\032\362\315\260?^\341\334\032\231\277\323\277\264:\343\3163\322\316?\253;@\303I\264\272\277\223\300\236\262\233L\256\277\210\301\330\347R\215\241?\2576\231\212\234_\260\277<\342n\320\003\007\263\277\322\346_^\236w\300\277\263\204t\213x\331\247\277\350\0231VHu\252?\020\002\334\351\302\177\213?\354\372\331\317\002\266\302\277\306\364\203\027Z\237\241?\272~\221\360Y\3361?I\350\220\034\177\210\225\277\006\234\337\037\004\205\334?\361\262E\252\3179^?E\264b4\020\310\265\277\2069\366\315\343e\277\277\007W\265\016p\223\267?&\221\204\300P\006\177?kI]\317\363\207\223\277#+\217\004\212\223\300?\212\214\001YY\020\214\277c\373\2074\245\024\273?\261\033\302/\242@\262\277\266\235\003\0376b\252?\256<UY\367\327\302\277XQ\241\021\025\245\306?\016\303{\024\200\364\320?\200\017\273\210\3232\231\277:Y\004j\201\003\225\277\261\0077\332\262W\270?\357%\240(\331\211\236?|\307T\r\010\270T?\377;M\315\030\251\304\277/\\\325I\206\247\315?\304\311d\317,\275\266?\215\025W\010\250\037S?-j\031e\242g\305\277\267\236\276\245\324e\217\277\'\2776\310Z\233S?5)\276a\333\332\270\277*\247\366\354\300\352\324?\217?:\341\270D\261?\331\237\004?gF\267\277\332#\321\036\231\373\254?0\315\306\335\335\261\266\277H\261\034I\316\030\233?\314\343\365y\305\253\236?\\G\340\253T3\311\277M\035|\370\222\254\225\277CR\241\217[(\276?h\021\225\323p.\216\277\303g\236}\271I{?M\177\221\202~\361\245\277n\305\253\331\351W\304?\220\306\001z\247\013\251?\304Vy\3126\376\301\2773\013\007I>(\211\277\212\013\3366\232\033\243?p\'\0209\360\215\250\277b\323Ycn\273\246\277\260u@&\323\250\265?5x\277\212=\241\274\277\370J\342\321|\t\253?r\225J\010\345\307\257\277\220\265\240\027\027\262\310?O\200\027\257})\242?VU\"h14\246\277?\377\276F\367\363\240\277\221\315\356+\232\331\320\277+\336\374\214y\266\270?C\312\021\333\362\237\260\277A\226\025%\254~\262?\034\220\343)\345d\250?\223OCY\005\202l?\216D\0176\007R\260?\220AT\213\355@\304\277tgoV4\246\226\277\323\332g\312LZ\255?W\376\031y\241\030\306\277;\323\300\32275\232\2770\330!R~\244\270\277\016\272\355N\356\014\227?B\026\375.\354\010\273\277\344\023*\302\370J\302?\376\233>\266tK\306?\211K:\360n\236\262\277B7{\303\333\014\311?1\320\350,\377:\244\277H\226\275\350\353\246\264\277\271\225\356x<\261\257\277}\224\265\312\332\200\236\277\223\237;\022\265\336\300\277\271\257N\320\367\034\307?\370 \372m\030\350\207?\213rZ<\014@\247\277{\320s\177\202\336\271\277\332\325\246\200!j\261?\255\273\324\370\315\224\273?\376\025)b\022A\234\277\207_\306a\233\343\273?=-CM\222\'\262?\032[\214\0230\365u?\354\221\207S\207\025\220?\255\31317\\\224\306?\217\214\362\373\266#\242?1\004D\323\300x\276?O\031\006\025\266\276\330\277\324\345F3\230i\232?\\\241h\242Wvd\277\342p\203t\331\345i?]z\341\2505\333\266\277\274\332>\212\207+\224\277c;\nd\343\314\272?\275\nA0\350\t\247\277\202\010\324\303\034\202\246?E\000\324S\321/\253?\242\342\376\311Wrc?\'\023\252\034\010\033\257\277\263a@\264\340\355\225\277L\231\016\177O^\303?\222\010\273\007X\003\307\277\346\251\214\002\"\347\177\277\342\225\2511\212j\274\277\327\266\026\226\267\232\203?R\311\344\014\221\315\325?Ua\377\226\270S\306\277`\314\376\352)\275\260\277s\343=\\V\363\255\277\245}s<\364\237\261?I\203\371\301\204(\257?\344\363\016W\214\347\242?9\353\035\231\267\361\267\277*U\362X\262\321\225?\177\377i\323\027\216\301\277|T\027K\220G\271?\364\245hm\016\231r\277Q\273\r+\nv\242?\360X\013\303G+\260\277d\264Vr*I\262\277\036\307\005\004fb\274?|u!\245~{\230?\007\\\261\221k\241\255\277Btx\245\362{\252\277+(O\244\346\256\245\2770\263\212\230\225\315\265?\006=1Q\2216\252?\223hE\023e\377y\277C(\300\242w\016\252\277,\002P\2726\254G\277\305\214\326\325\0175\265?\254\001`lAN\276\277\364O\222)<\001\253\277>\274\243\210\257=\305?\321.\230.\025\031\301?G\271%\265|\370\251?\337\2626i\2475\225?yxy\r\245\252\233?\377S\260\tb_\210?6t>3tG\257\277*\206\246{\300/\305?\336\026iwTp\177\277)\352\'\223\325S\224?^\222\240x\360\236\300\277\307\355\263\326\202.\273\277K\311\364k_\305\207?\244wS\3777\370\301\277\342l#7\355\334\257\277\271\223\006\2027\204\260\277\016C\317\216\320\240\271\277\322\032\224\336n@\277?\035w>BI\236\301?\255f\324!\201/\250?X\227\215N\330\033\266\277\010\013\010\222\244\'\321?\232<\230\000\024/\262?\277\260\224\226\371A\206\277Q\261y\374\326\245\256\277\326\227\224\257\271%\243\277\270P\305\212(\351\255\277\370;\221\"Tn\270?\376;\332\263\271\365\305\277\272\201\005\337\227\243\243?n\240\264N+\247\300?\315\231\2160\246\222\256?`M\316\350\303\010\202?\232qo\\\344\256\215\277)\360\177\300c\254\204\277\245\242CI\231\225\270?\017_\314\023aB\231?\263\232K\305\3676\274\277\317~\222\3643-\261\277\206|\314\0145\262\257\277*w\202\253\335n\305?\222GC\000\210!\275\277f\014\r 1U\340\277\005\312\276\251\330\235\264?:$\0332\311\261\243?;\264\233\376\034\034\261?\275:\224\353{\200\241?.\326\307p^\351\241\277/\321:A\370~\203?\205\360\025H{\000\217\277\264\335\222fK\217\277?\210\241QW\356\376\251\277v\273\337\357\207/\221?I}O}@\375\260\277\302\000\266\341\332\334\270?Al\271\360$\320\276\277\000`a\251XZ\323\277K)]\234\222\307\250\2774\t\256{\247\231\262?\245\017\337\370Q\004\234?\310]\rk\005\261\205?Rd_\273\373\277\243\277\341\377\354\326\364R\214?\214Z]\213xX\311?<;\234\310\3625\246?\361Zp\271\261Q\256?\351\333\242\360\333\036\325\277\367\257\235E\300\370\237\277}\356\355?\332\033\302?\013\324\273\322\347\205<\2773q\274b\352\347\220\277\255sR\216\307g\261\277\007\375L2O\307\216?ZU\274\247\256\254\275?\276Fs\210\017\331\274\277(\003\234TMe\232\277\243\2260{\252\n\232?\034\t\340U9#\302\277\3724I{\224\225\270\277L\377\272\177\366T\266\277\261\257\253\261pz\272\277\233\311\213\230Py\234?\002N\212\265u\036\300\277#91\021%f\231?\273ck\336i|\271?\366\037\036\241]\014\277?\026\233\365\315\201\244\247\277\273\262\224\221\351\320\303?\335F\013\354\217\333\264?\2740\211\367h\235\276?\221\232\347\363\025!\277\277\365\240v\"|\006\276?\306\260\342k\267\377\261\277a\330)2n\311\270\277\227\r\3254\035w\303?\n\224\023\360\255\241\303\277|\246%\022\326\r\253?\353\314+VC\230\275\277Cy&s\227S\264?\264\267\362\345\034\261\300?\0371@\242}\253\237?\017\206E\324M\001\272\277$\317\342E\005\364u\277\362\205\312}\336\032\262\277\374\2318\365M^\252\277\032\346\327^o2\262?\244\203R0K\206\204?\234K\221\314$\217\243??\311\216)\216%\307?(hq\262\334\000\237\277\236\036]+%\315\300\277\272\327\252+\356\004\273\277\312GA\'E}\325?\343\234\366p\237\330\266\277\250\230\0174\307\265\264\277\010Z\371g\306\340\246\277\026I\004\212Ml\303\277\272\357\027n\304\020\223?\t\307\345\032p\356\220?&\340,\271\320\305\271?\204\217\347x\377\001\204?\350\211D;\237\037\270\277\225\304\312\321\365\031\310?\355\346\206#\330\214\302?\347\337\323\001\376\305\266\277\326s\025\311\241o\251\277i\326\334{\002\200\265?\276W8\350\352\264\216?\007w\004\332_{\266\277\313Q\352\314\250l\204?\022-\n_\312\327\304\277S\223i>\273\225\270?\001\224\363_4 w?E`\227k\274\r\255\277\266\024\203\276\201\364B?\326\235\r\374\324\264\302\277\341\214 \230\277\244\314?p\323 9\371\355\263?\257|F\030W7\214\277\2674\3357\276\212\305\277\251\224\007\312\'\344\261\277\004G\342\014\221\320\300?\246\251\013\302TC\237?\026ou\235=L\302\277\016\257Xt\364\373\304?\026\013\314Kj8\226?\306\236\217\351\271\005\261?\235\257\324\322N\334\310?!\373y\273\035Ha?O\273\010nK\007\241\277S\373k~\2653\263\277`8\030*\020\310\231?\213s|\000\177\370\250\277kS\256\007\204\320\251?\255\272Na\301\013\251?\201W\003\346\300\304\261?\3673\260\254\"\311\262\277\203\331|\330\2674\266\277\006\337\3265gv|\277\237\362\356\376\035\271\272?\244\317\246sT\262\220\277G<\241\007\336X\261?5\363\333\306\326\265\221?\365\212\000\313%\211{?\222\251\253`\006\317\272?\227D\033\253l\025\234\277\373^\000\2407j\253?\232\3416\036\352\372\256?\262\261;\265Im\323\277\325\022#\004\t\205\303\277\033\332\001\355\354\351i\277]:9\222\355t\305?-P\362\233C>\266\277Sf^\341\177s\251\277\227~\325\\}\225\234\277\241\353\305{cT\303\277\316\275z\233Sbz\277\321t\277\"\304\301\246\277`y<\365\366\200~?Ve\\j\022\252\266\277{\315\007\217\316\304\230\277+S=\230Bm\237?\236<\021\274EJ\324?\225e\330\271\371\035\273?\356M\023\207\030>\260\277\334\024K\356\356\245\277\277\214\307 \211\246]\256?J\307\177\207m\261\235?<\234\307\375\273>\257\277\220\214\321\2301o\330\277\240\030}\210m\236\247\277\333\353\'\247\222\365\202?<\333\363A/\211\271\277\205\306\325\243\033\352\243?\335\313\213\0230\036\302?\335\003\323\314\027\272\302\277 \005w\325T\021\300?\014o\341\267I\370\271\277\007\272s\361\221`\245?\232\303\016\007>\323\302\277\311\264\034\235\244u\253?\r\313\353/\200\256\270?\002\255+E\247\333\264?7b%\035\223\341\307?X\340~E\343\303\237?\226\032\036\267\007\215Y\277K3\307\233\004\323\244?\274\370\3637\"o\246\277\033\2219\000[\377\247\277\215\023,x\274\341\255\277g\036\026~\347\217\217\277\030c\'\000\032\260x?W\246\034\320a\216\241\277\351\215\225\221\2470\300\277\364QU\001\000\177\254?\227\t$j\026\314\252\277\275\347q\264\345\270\267?\336\034\331\023\\\321\321?t(\362\0136\003\262\277\237\027\300f\316\244\233\277\347\266\344\321\261\007\235\2777M&\022\323^\251?w\237\360\351\004\375\207?\364\323L>w\256\276\277tS~\201\224\372\311\277\360\352\211IB\303\252\277\247\036\224\356B\335\262\277\324l\245\342\336\310\227?X\031\374o:\001\177?+3\313\376\225\305\213\277\207\241\032\261\360\r\241?\243\022Rr9b\205\277[\323\370\310\336)\206\277h\335\035\237\325\332\240?\030\306\300\3445R\245\277\347m\277\216\rA\271\277\025\240%zp\020\243?\306\'\356\035\2149\317?\343\020\275{F\232\302\277\342#\260\277\244I\226\277)\3460L\374\261\275\277\317`!\200\334-\260?\236\325\'l\303\235\311\277\234\002\266\004\272\033c\277*03}\224\345\260?j\277\301o\255\013\205\277?\001\206\227O\300\300?\262\322\333J\347\372\247\2774\00662pn\275?\220h\000S\016\000\246\277I\031\207\302\277r\233\277r\001Pv\231k\221\277\245\004nF\375\342\261?\356/y\243\275L\242\277\236\304\215\313/\r\270\277\032\335>\321U\305\235?\277p\354\341#y\270?\0069\202a\256\271g?\353\305\263^\177\261\263?\250\023\247\305\032^\302?b\355\205[\025L\212\277\2164\203N\300\242\270?\312\325\247\302\234\020\310\277\265u\351\257\365\226\210\277\345a\265\216\225\231\301?S\304K\363\325\031\273\277s\027\356\335\006\205\302\277\334o\033nC\001\237?\343\312\270v.\262\251?\353\312\0074\r\356\304\277L\245\030\260\006\323\300?\252\224\2251\027\251\246?>\314\375\007\374\256\254\277\343h\315\361\363\255\303?H{)G\274\257\260?\023\352\236\016\233\237\254?\236\263\000\246\355>\244?J\017c\312\322\217\236?\373\273RN\313+\224\277O\226\301\214bO\253\277\236\321\r\007\247~\275\277=\303\"P\t\322\260?*\250\007c/\360\232?\'o\227f\025\344\263\277)\273\314\235h\230a\277M\035D\316\254\337\240\2774:\022,6\372\271?\257;mcT\263\322?\205\252N\226\206\035|\277(\241g\222\206@\262\277\341\352\246e\263\314\304\277\321\377\321\247\256I\242\277\246\002\"b\217\262\262?K\374*x\375\022\300\277BJ|x3m\235?b+\177\000\370v\261\277\276\t\217X\230\314\265?S6\266\n\3256\310\277\025/k\222\200\371\304?\005Q\351P\357\030\244\277\010\252\357^\n\312\241\277\275\013\200\316\221\010\301?n\324\237\343m\314\317?\253\336uN\275\032\204?\221\256\332\262\361\261\227\277\203\025\036i\265\006g\277\272\0378\305k\301\252\277aQ\267x\263\355\261\277\254u_L\000\354\276?\322D\330\314^2\247\277\263\213\013\323v\221\266?\301\322\306[\202\257\277\277\315\304\307\317\322\234\206?\034\246fsU\210\250\277\005\255\025\214v\262\261\277=wlV\226{\247\2776Y\013N<\032\244\277&e\255\230\233d\255\277\217\210|\260\276S\255\2771\025\326q\231Zw\277URC\360*\266\260\277\310MW\021\355\327\254\277\363\025\342\342\375\016\330?Y\3045X\014`\254\277#SWU\262$\240\277#$eE\207\334\252\277\345E\204\203\3665\240?\250\t\252\302\347\010\246\277\377\233\233o\'\'\265\277\247\026\203\025g\216\267\277\307\374a\245Od\260?\013zo=s\023\227?\'eag9\351\226?\336\210h\247V\322w\277\034\007\352\315\010\036\300\277\335\322_\330\031\001\241\277\372i\321\317\211\340\332?|Z\035\207\322\016\227?\232\216\223H\007\025\253\277\241\244\337?S\275\240?\312\205\310\344<\211\225\277\322\220\246\222\365\030\257?*S\360\253C\260\300?|\244e\3625\330\214?SQ\270\020\237\330\227\277;\027|1\256\354\270?K\026\350pp\253\323\277\307\371d\211\367\243}\277`\322\342\353\223{\277\277\312\325VXM\373\273\277\014\344\363Q9\365\320?\326\227M\t4\254\247\277\371\303\007\300\324n\243?\311\262\261q\027\"\231\277\3347\035\001\220f\263?\243\206RJ7\302\225\277\215\275\355\306\364\332\266?\r7\346\033\331\360\315?Q1z\305\352\000\255?\213\324(\032U:\263?\367\324\315j3U\325\277\330\234\312\344];\226\277\023\205\367\260\251\300\302\277\352\030\002[R\272\217\277?t\255\033\262,\272?Kw@\335\340%\227?{\352\220\205o \274?\035\317h\251`\236F\277\236\005\222\210\005>\303?\305\0042\263X\340\221?\331&\364c\231u\263\277\244Ki\232\247\350\225?\nF\212\255\236y\245\277\301\010\234P\203F\272\277\252\251\250C\261\\\307\277\323\010\000\033l\200\250?\267\037\3554\020-\240?\274\336\360\257\357\255\315?\005\260\261:\356.}\277\016\306\037\351ER\260?\3358\026\377\2339\257?\364(\233\212\361\236\263\277\3247\374\n\262S\260?g[\260i\375\204\221?1\344\355\273\360\242\240?\013\n\303\223\342\203q?5\212\025\214\177\370t\277\234\266\361\267\032|\260\277\000J\032\356\201\323c?\247f4{\004\313x\277M\276\343\376\342\032\261?\247\025\331\245#\201\223\277\331\326\002\334\266\\\202?J\032\222\ro\254}\277\255\367w\263\026\031\310\277\274\336\375u$I\302\277\032\t@\257tY\247?\221\326\210aP\322\273\277\t\342\236,\330\206\304\277?R\316f\367\244\303\277VV\222\262H\263\235\2779\027]\305v\237\305?\325\031\271;\336\310\240?F\327\307\t\3254\252?: =\177\220`\233\277.\352\352\367\250\325\322?h\2230_\224\360\223\277\r\002\357\r\313c\252\277/\375\326\305\177\270\266?\255\271[\240\307\356\270?\361\224\312YC\333y\277\370\270\352\326h\007\211?\215/0\211|7\252\277\034J2l\367\230}?\215\\\024\365\030\357\204?\255t\223\207\206\343\272?\336=\305\213\232\351\264\277\202[Q~o}\221?\215\301\205\001\241\373\303?\t\343o\375\\\331X\277|J\321\207i\321\276\277\230nb\316\217\262\254\277f\373T\264\246\260b?9\'\360\216C\013\270?v@XJ\"4\263?-\204(hi\352[?\366?s\246\263\022\234\277cx\200\365j?\265?\026\270H\252\233\336S??>q\311n\026\301?\205\342\376\305\272\022\321?\240\0303\312\333K\251?\326:\361^A\257\271\277\241?\372y\241H\307\277\341\035A]?v\312?\365\337\213=\330\265\225\277o\337\262\243\2068\252\277QK\223fVs\300?\r\246\313D@#\277?\2322snIfy\277\274\033\270\202q9\261\277\032}\264\300\024\207\316\277\2060\275\376\0373\277\277\313\002\334\226\310\332\231?}K\224Y\277)\230\277\362\206/\027\345\234\232\277\333)p\231b\252\246\277\003\261\n\321\360\027\222\277\004\311)0\310n\300\277\025\250\235u\035\027\253\277\330c\276-\021Wl?\342\216\231)*\235\317?>\313\202\215\r\377\272?\240\030y(\030\260\271?\0075=d^?\205?\016\341\323\362\256\367\266\277n\225\034\256\315@\271\277x\371\215\340<\253\272?\235\345\017\241D \306\277\022R1\230\335\n\277\277\022\241\002n\253/\271?\005\231\374\014\375t\252?\344~!.p\010\305?.u\323\277L\233\242\277l\235O%\375\265\307\277\262\274\213\t]\032r?\312\304;\214\365\017\247\277\352i\326\331\374\264\263\277\271\354\345V!r\276\277bs1\251e\001\300\277\320\347rX&S\244?\214\234\341M\365\334\221\277=\213~\247[b\270\277\314\251\035&Vy\267\277\010(\311U\211\264\260\277\r{\346b\241\250\263\277\371s\235\205\013\266\306\277b\234\272+\307\000\267?\362\325\353\236\263\005\261?hd\026\354)m\307?Q\342@\356\225@\204?M\317\373\371\027R\302?^\227\006\037\031L\252\277\202`\242\016\357D\276\277:\235\345\270\316\003\227\2775\262\303.\336\323\221?\274\223\"+\345\335\333?\356\234\312\300P4\305\277|\226\t\256\236\276\270?\361i\346\336uy\270\277\022\340Uq\324\310\266\277\017\325Z\301\220\350\232?\032\373\250\372:\303\261?kb\027\240q\351\316\277po\255\245I\323\312\277\376\202\311\033~R\265\277Z\0018\r\242%\224\277\210\243\243\235\007\241\307?\267(\030|Sl\261?\243\375\306\347\034\033\301\277V\215\275\226\340\375\312?Hu\211S\371d\260?9,\023q\345\307\277\277K\027\301\361=\031\261?\\\261e_!\336\177?\321\212\247\371\251\003\266?R;\255\310f\313\305?\260\367~\372\364\243\223?\230\361x3\306D\300\277\200\367\360\302\343\321\230?\027\312\263\222\034\033\257?s\024I\273\026\347\244\277\321\273\2459I\014\304?1\212Fg\242.\251?q\014J\236\345\327\271?\351\004\336\277n\231\227\277\214d\200\354\260_\261\277w\305.o\310=\301?\031F}\376\216i\243\277\210\003\316\024\030q\217\277\314\300\3431\250[\300?7\000\013\313B\354\244\277\3232\001\254\344\"\225\277a^\362&\272\346\224?|\003>\352e\224\300\277\034\010\013=Z\214\300\277{\226\227\300.sq?\025\032\022\363\031E\254?\355m\305\030\032\266\276?\245\277\"&\244\261\245?O\325\021\230\215\"\217\277\177\242P\244\331h\261\277\365f\345\372\017\025\306?@>`w\240\371\256\277\206\005\210)\030\372\260\277v\000Z\337B\303h?\315\022\376Tdl\306\277jd\343;\244!\277?s>\034,\2142\266\277\177\355N\246\370\234\211?\234|\261\213Al\302?\245\004\306\242\235I\271?\300\310\010\250=\314\266\277\305vg\251\212\035\244?\247\254^my\252\270\277\351\276t2\024r\317?\307\212\216\372E\214\267\277?\270\247\337\377\230\263\277\021\004c\251\346\024\314?\333\245\264\375C7\263\277\251\224{\203G\001\212?C0\'V\020q\204\277u\243\000\t!\213\234?\260c_\300\007\245\200\277N\030\205\031\344D\246?\213D`\005[\252\200\277\224\235\203\'i\240l\277&\\8\000!\373\236?\347\n\021\304\307\330\301\277P\262+\265\310j\254?q\263\201\370>\200\263\277\024\016\321rK\265\273?N\007\271T\n#\274?[\344\360\277\223\221\274?\226i\311*\220\016\253\277\336\333\030\251\247\363\266?rnn\355Q(\312\277\017\275\350\004\226e\212\277\325Q\nV\307\321\306?\367\023\363_G\203\241?\005\222\215\375\274\"\262?\022\n\252\3467\272\241?9\\\013%\004(\240\277v\361}\361\231\236\310?W\257t\333#\315\200?\354\270\370d\3717\267\277\304\024#b\303\273{\277\t\360\371\334\366\345@\277\257\202Pk\026Z\230\277\007\376\2169- \271\277k1\010t\263\243\235?\373\006\220\334?O\310\277\321\374\202<\"j\261\277\255\205~\244\211\343\270\277]<\215\246\272\010\252?\246\300W\022(\306\245\277\300y\357\007\342,\206?z\263\275\312\210\351\267\277nTW\366\0208\301?\030\217\362D+\226\272\277z\270@\0266t\241\277 \352\006\200\237\235\251\277*\367\275\226\033\310v?\362\324\307\000\340\337\221\277U[\332\022)n\253\277\343\236\345$EJ\317\277\372\030\0069\\\264\264\277\300\016\004\342}F\221\2779\335\255\021\365\214\302?\330\205\346\224\370\364\221?\204\372&\377\262\356\314?\346\302\357i\014:\265\277\203Xf\033\261\365\317\277\2147;!\334N~?\274_\017v\231\254\314\277Fc\r}eg\273?)l\023\036\007I\303\277_\036\213\024H\256\211\277\002\360\245\343\275\026\201?\212\340K\207\267\'\263?I\271\231@\240\034\242\277k\203\257\353H\321\252\277\326\216t\223\205\246\311?\t\303\327+\326\374}\277\223Q\010\353R\177\267\277w\264\277\007\375\221\240?-\357w\350\2406\320\277M%f\216lJ\246?\0020\013\016\272\337\271?\225\201\264!~]\303\277F\021\334rS\250\265?\003\247\304ZL\363\246\277c\344^:\257 \313\277\377\327\234C\363=\251?\032-,S\355\274\240?V\345\373\031\375\365\263\277\311\"\323\267\314\216\325?%\2147\316\030\255\243?\020h\245h\336\007\260\277o\251\237$7v\271\277\002$\202l\325\342\254\277\315e\346\354\215p\233\277U\311\363S\333\250\276?i3w\351\221@\277?v\247w\336\227\274\232?MZ\303\233\037\365F\277\007p\337\311\237n\241?ZWg\326\237\330\201\277h\207\355\303\320\035\244?]\026\014\232\036\016\272?\n\225\3047\rr\252\277\347\206\'\357Wi\256?ms\177}w-\237\277\223\026\027\361l\335y?f\363\321\2459\336\271\277Fg\"4\230\306\256\277cC\031\363\354M\244\277\210\235\200\327\327`\270?\215\031\'\201;\243\314?\327H\r\336\251S\251?g\323i\354\0169\241\277\177\355\302\221\262\221\310?\274.~C73\273?@\255\335\232\304f\236?\027\314m\022\003\356\262?\006\205#t\367J\242?2\323w\220\014\370\260\277t)\321\230\262#\302\277\274Smq\036\343\261\277\265\025\023\3448\267\255\277\267\201\376ma\177\250?\377m\306\347\030%\270\277\235+\n\245.Z\277\277\377\363\317\013\215\236s\277\307h\202\224R=\260\277U\201\372C>\032\256\277\236\t\214\317\340\224\255?\\\"\210$\266\267\223?\254\3717\307\237\001\275?J\300\260b\346!\257\277\256[b\3563\206\264\277\264\363\233\232Q\220\273?\001y\270\237\236z\250\2774\200\002\261+\"\234\277\306K{\031*G\270?L\003\311o,\030\274?2\021\266$\311y\221\277E\013\343\217M\030\224?O\301>2\326!\272?\216\t8\201N\231\300?\363=(\273\247\227\255?\r\337\374\323H\262\250?\234\255\346\361\314\244\230\277{R\231\\\3608\232\277\027\2212\"\377*\263\277\362]\022\221~\027\245\277\305U\310\017\217{\304?-*<?\tO\252\277e$a\200\2029m?\010\340 \225s\312\304\277\351\333\233cZ\"q?\272\312\373Y\004\325@?,\333\375\274]\264l\277\373u?$\252F\330?`s\310\377tL\232?\tB\317l\206\337\260\277|}\037\300\307\361\316?\3303C,\354\311\225?\036{\025#\007\260\312\277\340\237:\233\t\355\300?\314\365\t*\023\"\243\277\n\357,W\264\227\261?\252\233\227\236\024\006\262?\251Q\037e\237\307\260\277\373\272z\307\357?\242?zw\031Sv\344\244?\264_\n\n\227\225\263?\010HR\364t\301\273\277=C\007\0148\037\250\277\234(\315\037\007\361T\277\"ze\230W\363\245\277\026\273ZRu\213\221?\023\353\245\361\305\332\272\277\331,W\n\207g\256?\242\001u\013w\230\265?@\310i\030\365\215\243\277*\357Alu\214\204\277\332\202\257\363\025\306\271?\205&\024l\221\307\301?\313\300\331\307k\211\224?\367c\\\317\013\312\264?\326E\203@\212\260\251?\363rs\222\367\371\242\277N\214\307\355K;\276\277\001N\365\322\245j\311\2771>\213KF@\221?S\257\004\247%\026\240?\023\251<Dz\217\300?]\323\266\244\036\244\253?\243Pc\272\351\220\271??>\213\274Dk\274?N\323\372\330\036l\261\277\264B\336\314\364.\303\277#\301%\014$k\212?\217\033\327\363\211\324\305?\t\251MX\001\027\241\277\252~\276\033\201\363\225?\213\357_y\2117\235\277\271[O\355\207\020\307?,\024\326\372\301\243\277?\024$\301\234j\002\273\277\246or\023\235\256\304\277\333qK\310[\230\306?\242\247BC\351R\257\277\2635\234*V\236\270\277D\234\366\320\364\235\253?\215j\365\024w\362\264\277Z\247@2\347K\247\277.d\013\206\313H\252\277\002\025\252\036eJ\270\277\247\260\256\364mN\243\277o%U\200\213>\246\277BS\001\310\342t\315?\300\226\304Y\232\371\177\277\224\024\230\205\365;`\277W\0357E\372`q\277uJ\271\212%t\243?\357\353\331\203\260\332\240\277\023j\312\330j\016\266\277\301\304k\016\362\223\304\277\017\200\250<\323c\245?*O6)\207\317\270\277\307\333\265Z\021n\271?x\232\313un\216\205?fc\370\r>\234\244?\261M%\256\014\324\300?\256\222v\n\0004\260?\003\233j\306\320\274\242\277\020 H\216h\024\257\277\320\025\267\207J\376\266\277\306\253\377\376\324l\266\277>\324t\311\252\'\240\277n\252O\020\327\'\264?\021\241\034\025pr\233?\243\337\245\231\332\365\257?\260/\232\302\335\240\203\277 \\\235W\021\317\267?\026\203\314\272\232\032\311\277\035>%\"\264A\250?\204\266t\024\204\330\266?D\204\0109\201\032\310\277\350Tkb\373\002\244?I\342\244\313{\016\220\277=\240kY\360\343\304?\352\201x8|\345\177\277\345?\022\217\201E\205\277\\\225q\215q\325\307\277\3646J\351!\300\261?\213;v\245\300\031\270\277Z\005X\221cJw\277\342\037\357j6\211\237?X\342\245#\360\217\240? \235\307\207\\\376\274?\237p.)\346\265\275?2\372\337\000\024\272\242?\020\353eA>\270u?\366\016\233\345(\360\310\277\257\27225\270O\276?\375S\353\313\367\257\310?\210>\270\r\021j\255\277\341{X^\305\233\263?E+\3416\376l\201\277.\310\360\233D7\301??\004\343\006n\311\271?a\343]b\276\354\236\277\313N\202\227\226w\253\277\367\021{\264\237\240\227?\001\211\344\016\271\347\244?^t\034\016tR\324\277\274\2259)\036\324\317\277]5\2225\210\323\261?\257\342\033dy\304s\277\337\207\211\256M\r\324?\264\014\273\204\270\354\275\277\362g`\336\033\221\261?\373\017\372\234\262\275\277\277u\2263\014]\203\263?E&X\024)\201\221\277=\273L\254J-\216?\273\017\271FD8\270?\331\320\026\234,\352\225\277\016\370H\"\006\370\257\277\242\200\006\207\032\357\300\277\363\317iJ\267\321\225\277coH\000Q\257\305?\311.\351\232}\020\326\277\027\354\332\356]\n\273\277L\010\004S\325\210\206\277\'\0221\215c\323\255\277\240\377\267\334O#\251\277\336!\254E\023\\\271\277\373 \236-\200O\242?\366\322\026\341\232`\233?A\200\203\364\272\305\240?\220\024P\366\210\376\264?\030G&^g&\237\277\326\220\370+\340\322\246\277\275\324\024b\251C\210\277\240\377\311z8\345\275?/.\016o\035\226\204?<\325\033\242\220\244\260\277\356^C3\352Dw?*T\342J8*\266\277o2zj\201\375\243\277\021\277/se\373\273?\354N\377\357h\346R?\027{\005Dq\272\274?1\326\271PK\330\305?\247\375WG\027\236\221\277\206\312\332\03120\241\277u\005\232\355I \261\277\353\341\366\002\035\344\275?\022\265$\317\250j\243?\217\030\3420\231\311\305\2779k-.\351\010\324\277a\027\270\331`k\260?I\255\366pj\261\263?\247\342\324\214j\322\233?\247ax\220\341B\226\277E\251\277301\201?\206\035\343\361\216o\254\277\240\353@\350\264\213\257\277\r\326\013\341\2376\234?\212 \024\212*\204\237?\021$Y\223\346.\236\277v/.\030k)\276\277\262\367K\372\2562\300?G?>\326\365\223\276\277\301K\362\376\177/\316\277\342J\"2\310\341\263\2770<\312\200\270\261\237\277x\334\245\235\303\n\210\277HR\324\202\203\270\262\277\203\361r\301\205[\244\277\316\346\256\">\370\301\277\237\316}\375\200:\317?\025\347x\257}M\255?0K\202\236\374\366\222\277\026*\270\265\346`\302\277\243\023\202\252zb\224?m\022\305eX\004q?\025\007q\333\322\030\270?\252\356\300\201\0350\227?\366\312b\206\307>\303\277f\344v\321\240\247\243?\334\366\370;\310\352\214?T\224d\372\007\253\261?\234\334\035q\334\234\227\277WI\340Ej\214\205?/\213\220\317(^\224\2778\024\346\001\360\257\250?p\020\327c]\277\221\277\234O\214\310,\354\266\277\365\200\223\343\302\003\215?/\303\330\363\340\313\177?M\034\234\352P\351\234?\270\276\370w<\255\225?\206s}No\301\252\277\006f\037-\217\332\245\277\322\260\366\316!\336c?\337\272\307\220lK\252?w\246~\022\213S\213?\256B\356\360\233\224\275?F\252\320\332l\214\311\277u/\254\366\307Y\211?d\025\253.\270\307\300?S\203l\257[\337\333\277+>x2_z\260\277K*hi\311D\247?\230\013\250n\323\357\273?\272\252*\226\224\312\322?*\366\253U\211\301\216?i\362+\313\357A\300\277X.(U\234\350\241\277\312\032\320\243(\332\265?m\252\347\310@\223\247\277@\363Z*p\225\310?=l\235\026H\215\267\277\300\313S\364\036\325l\277\371\301\'\202\3274\275?\231\302\252\037)\353\320?\307\366\241\217Y\026\221\277\334\325\334\363\025\245g?2\247\002\200\210?\261\277\366w\370l\210\266\323\277,BV\2205\266\224\277\207<\337es\033\256\277h\307\231\207\004\017\245?3\240\210b\2045\261?\207\200[\341]\316\212?9DF\327p\300\251\277\326.A\363\360vt\277\252\33640\333f\247?\344\322\311\361\344>\270\277\"\006\271\330\304\227\304\277\254\020\020W\344\361u\277\220\343\351\3666\014\242?\362\025\271\262\t\257\273?M@\252\244L\r\317?N\271\356\224E\207\231?\0260\301yG1\240?\023\340T^;O\277\277\263\tE\371w?\262\277\365\361\005\370-\030\270\277\210\3544\276\022\377\247?\222C/\364(\347\226\277\366\341\353*\215l\206\277i\265\320\366\362>\267?\007\327\023\303\245G\311?\032\177\213d\035D\214\277\234_~\000\3411\262?\337\024S7\342\032\220\277*\005\313\030.c\260\277\266\t\241_d6\262\277w\037\274\245\r\341\224?O$*8\014R\266\277\237\217\272\207Rg\235\277\343\320p\310\366\020\207?\260\016\270\277\211\260\222\2773s\327O\373\226\320\277G\214\247\357\377\360z\277g\224\002\214@o\232?a\250\301\326\250\030\261\277>i\317V\311+\206\277j#\r\340yW\311?3\304y\340H\363\304\277Xf\255cS\355\300?wE`\334\202\344\255?\230\356\022\317\363\362\250\277\240\220\225!\203\030\261?\331u\002\253:\264\275\277\025\025\2020\325\335\271\277S\362\277\244)\261\254\277\336\257\234\274s\370\302\277\267\363\333v\236\315\253\277\233\263\267\230B\240\251?\207k\301\267Y\315\272\277\250e\200\001\245K\240\277\221O\255\373\177\350\242?\352\344\204qo\316\272?\314\\Y\302\016y\306?\367\327OVPr\261\277\'\265Ne\342\227\241\277<D\005\037\244\207\267\277w\364\027\341\017N\261\277\032\350\177\3525#g\2770Tj?\232\265\265?\256\305K\325\036\360\320\277\247\261\325\215\021^\274\277\370\313\203E\2163\225\277\207\nA\274\227\343\301\277\221\256\276\023\017\331\272\277\243e4z\313\223\261?w\376|\006\030R\267\277\234\351\022Fs\021\263?\262\362t\02726\235?~\373\376Uy\272\254?\322?\333C\251\361\255\277\357\205\316d\004\316\267\277\215\374\321i\363\230\225?@\361\245q\2528\277?\310\222\317\206\303!\303?\256\260\016j\327*\265\277\006\234\310\326V\332\270?\244\222M\267\312\213\326\277\272A\023S[\320\266\277\263\364\026\260\210\001Y\277\252\214Q<]\353\303?\220\305?\277+\260\270?\005\316F\006K\222\236\277&t\033\234\277\037\240?\013)\244T\342Dx\277\367vZX\260\027z?\210\021c;\243\305\212?\277\314\365;\361\252\265\277\305j\243\226\347\274\314?s\021\314\316\273\307\276?\350\351z\267\317\317g\277\236\253\364Q\347\033\312?\366\320b\247\244\254\227\277qQB\333ev\303\277\245\314\020\367\035Hu?\306Gp\213\231\306y?\370\204\257\321\362\271\245\277\363L\206\032\313\302z\277\215\002\241OCI\273?w\301;\300\260F\221?]\215\023\310\024\005\220\277.\355vO\241\231\206\2770\023\244:\222\347\205\277_s\n\034\275\311\242??\006E=y\213\217\277\372\"b\305\224\201\254\277\007\300\357\005\225\216\224?\342K\304\250j\002\242\277nWO\226S\217y?\354\324\254m3\336\331?\375\242w\372&7\251?\256\023\237%\247\314\232?\205\030X1;\233\272\277\342%\003~#z\264\277\221\036\305x\236\007\241?\313w\270\233{\246\301\277-\346!O\001j\311\277K\007qC\302\203\210\277rc\233\354f+\261\277\276\304\262{\277g\221\2771\370\0307\025\263\243?-\244\214\353\274\331\262?\206`\212UH\'\315?UE[\203\330\\f?\317\007\243\232;\276\272?\207\311\025\343\323\307s\277\215\244\003\036\211\0312\277\023\351\376\376(\013\311\277*\232v\221\354\361\244\277b\243\240l\005{\232\277\305\231\252\355\313\217\261?\"\270 O%\010\253?\362\347\022\206\225?\246?\033\244\250Q\364\357\303\277wh_\300\260\002\272?\376Z\255\307\016\026\231\277G\206,/\340\313\271?U\243\331\334\275\375\314\277amQ\275/\247\237??\244\352\324\345\302\230?\371`\346\355=\263\276\277\276XG\347\022\007\261\277\241\255\302\343\377\236\262\277\353\222G\203%o\205?\2071\302\374\324\341\227?\026\003\241e<\035\263?c*g1\324 \300?w\310\t!\013/\257\277\336\373$\235\372\222u\277\206\347\374\253\010\303\250\277\365\223\030s-\035\275?\361\366c\'\n8\315\277p\311\271\210\371\232\273?\314Z\251@\200\t\307?O>\313\354\356\243\244\277C\211o\255\014\342\261?\030\254\312`\343\004\227?s\255\310\226\025\245\312?\231\200.s9\356\311?\270\366\221\255\263\230\231?\367\n\034o\213\346\220\277N1(\226\276\374\301\277]\201}$\313J\271?\263\207\214\220dP\263\277=\014O\002j\321\262?mHEL\364\367\307\277h,\037\322\325\307\253\277\322\340\336\330\020\252\247\277Sv\335Xi\356\242?\313\214wW\324\302\241?\2340=\025\365\247\272\277T\273\000c\370\322\301?\032\303\265\237\226%\306\277\356\323)\345\345\340\217\277bA\\=f^t?\205\305\233\304\2519\242?\215\350>S\256j\247?\226\325\221\211\307s\240?\267\0370\264\007\'c?\351\271-,t(\313\277\264\021{\344\r\356\264\277\222@\232\303F\274\254?C\372gv0{\256?\267{\014\016\246\255\265?r\335\346\233:$\272\277b0\374\350\252\265\265?r|\224\255\236\346\201?\247GaA\314\023\252\277\212\001\352J!\347\241?\360\332p\234pr\320\277\341\031\262\335/X\260?\277\016\t\355\277\220\210\277y\030\235\1771 \270?\034\266\354ZB\237\311?\261\027\177h\366\216\264\277\034`\265tgm\260\277a\304\3130u\277\313?\310\\\001\233\206S\261\277\376\362h%\334i\225?1\017\221\216\332E\273\277\250\024\263\274\2450m\277\r\224d\365\325\363\240?u5 r\347\025\271?\2221S`}X\320?\316\267\216\027hm\300\277\007\205rw\322\204\235\277@\n\255/\200^\311?U0\035\rd\256\303\277y\221|\303\272\255o\277fVF\365\231(\251\277\266\tfA\232\225\312?hU\332\375\357^\261?22wg\261\345\252\277/>h>\010\370q\277\035\371\312Ut-\253\277\030C\372^h\016\255\277\347\352S\360\027\321\256?\337.Z\0102\355\277\277\335\251S\330}j\266?\344\352\021*\320\356\205?\346\2322\213\207\344\255\277\3454\271\355\250|\255?\251\205\"D\016\020\271?\336\345\"jen\263?b6n\250\325\302\302?\264b\300\021<\017\257?\306p\225\3654\203\226\277v\267\3510c\351|?D\240\366@\240\242\257?\355\250\2607\373\304\245?|\353\367\236\221\030\237?Hj\323V\333\320\272\277\004\247\276Y#a\237\277\255\224\346\3356\213\271?\341KE\207\rF\252?~G\200i\350_\221\277[@\207\236\210\025{?\022\255X\267\315o\261?r\326\204!\356\253\313\277\007\017\335\331\276\016\212\277\373\263\205\344\262\256\265\277\216\005EO\205L\264?||A\362\026\000\306?*\333\337\336\351n\262\277g#\210\024\271\213\300\277\303y\'\237w}\265\277v\303\246y\350\032\211?0T\276p\326\300\303\277\357p\346\354r\206\252?;\\\272\302V<\320?R\371\024\267\200\326U\277\335\nApo\364\250?sfl\351\315\266\272?\000Y\255F\325\351\225\277Q\353\330\326d\237\247?\031\342X;\002\234\243?\227\341\314pn\357\266\2777\234|\251\367/\177?\341Z\0326\233\351\246?\364\3509\332\205z\260?pd\221C@\316\265\277\345Y\214w\016\343\260\277\016\217d>\270\361\266\277\250\000\007=t7\327?\370u\247\206\251R\200?\352\0374\330\030\360\246\277\352-\006\035\333\217\272?\036h\007\215\037\356\246\277\363\0068\346z\241\256?\255\205\275\271\273pd?C\312\314\235\234[\263?\202\367|\026\242]\246?\022\266\202\246\301-\277\277r1L\005\006\017\216?\365\236\367\256\317E\271\2775p\016-j\243\303?Rq\303\242\021F\263?\263\233\340\2368\250\264?\245~\030\320\027R\306?\224\343.\226%\203\257\277V`~\236\035U\211\277\240\373\240\337\227\346\307\2778\231\370\273\\x\204\277\350\272\233O\272k\306\277y\315f\004w\013\307?\377\322\252\322Z\212\245?6\326\211\235LH\230?m\014@o\307r\274?\177\200w\256\237\220s?\321\017.c\246\303\266\277hH\341\333Th\276\277\255X\365\225\245\312\262?u\"H\225(\277\216?3\031e\233\251G\246\277\352\014\236\n\310)\274?\311\350~\323Qb}?\252\224\250\256\201Q\203?\371\034N*\2362a?\340\256\351\275\364t\263\277w\241\260:\240|\302?fk\252\344\014\006\301?\333\017\020\233\023\304\306\277\017\016iel?\205\277\265\t\245\235p\260\210\277\317\245\224f\3755\264\277\007\207Hq\024\314\240\277-t\224u\210h\267?2\"\350\246\267[\240?j:\263\205\351\275\222? +#p\207\227\265\277\312\334\022\024\210>\211\277\t\241\277\032\267\013\314\277\266\200\363\034\355\261\306?\311<l6@\"\276?\337\346\247\2237!\262?Jx=Q\270\222\302\277(\214\335\351\246w\260\2779\262ia\035T\224\277\222ik.\304-\204\277\037\037{\2347\312\325?\351\225\303\013\234\032\225?V[\242\357i\271\302?\235\271=\3679\034\241?\244nL\017\220r\260?!8\222f^\217\246\277\222t\317\275\357s\253?t\374\333U\351\021\307\277_\343I\032\311\217\200?\034\265i\204>\226\250\277{w\273mu\007\315?\230w\312\343L2\222\277\314Nt\333\361\365\250?\034\226\014\324\217\337\250\277=\031\351]\217\003\325\277\304\271C\270\360\037\221\277#\256fy\014f\270\277\237h\201u-\213\236?S\243\226\207\225\344\261?\256E\255\224\311\322\234\277.\tNebv\264?\226 \272\210w\'\322\277>i\210\013~>v\277\3216\340\357\303\240\253\277jRx\240vT\207\277Jq\352w\272.\201\2777Z0I\205\n\260?\266l\345\244\024\333\270?\211^\2066\327\350\301?\202\265\250\273\317\254\304?uk=e\345\341\210?\207[\210\240>\217}?\302\271\033\031\252.\201?\366\357\250\333\0239\266\277NqR\362\034\262\302?\201\210.$0\255\241?\210\306ZC\177\330\225\2770e]t\"6\234?\366\177\2347]Q\305\277\037IZ\031\020\001\261\277\031\267\226\003l\177\242\277\307\375\tKE\355\260?\007\316\2433\300\251\310?\035\037\3635E\334\257?%\300:\002\036\002\300?8@~\313\271\327\265?%\210\177g\312\227\227\2774\214\275\374D\000\242\277\027\271\334r\240\261\266?\301\342o\016\221\307\231\277\022s%\220\337\343\267?\271\252\370\374\034\003\273?\027U\377\270\2555\302?\333\241\261\247\\w\201?%[0\0318\367\301\277+\022g\311\372\270\300?\3531\256\231\002\027\332\277\336\225K\206\330\256\263\277\022>\206Z\230\236\276?\361\354Z\311%\242\263\277\253x:\364D\034\306?LHL\271V\306\220??\236\t\362\334\226\254\277\335\346\311\213\031\r\332?\223\005\301\370q\372\212?~\"Z\251\210\363\265?\241\017R\273A\337\223\277\3778\003.lP\247?*F\177]\264\254\252\277\313\305\010\254\304\266\261\277\233\237\307\214\241fu?\211\206l\303\347\271\220?I\004t\213\327\304\205\277,*\325\021\n\365\221\277t\225:b\242\024\264\277\2700(\243\232#\262\277R\332\307;\353\331\226\277q\374\200\"pM\311?\335\t?\324\303\333\223\277\213\250\340R\221\034\274?\001O|k\230\013\304\277\301\226^\371\021\254\243?\340\320B2D\177\256\2775^\024\345_\033\273?\265\350\371\263\315\354\304?`\322i0\205\014\241?\t\260@\363\037\316\274\277L\343R\360Gg\237\277\203\033\200\262{\013\252\277\231j\203IY\315\215\277\234\220\221\240\361\'\267?\251\0271\223\022\352\273?eTWcyb\264\277^\360\330\242\255\024\211?9\234\031\2059\277\323\277\3728\033v\323.\272?\\(\2408$9\267?\303\021\327\250\016\267\322?2`\005\345\330*\317\277T\202\036\312MQ\201\277\026=\006\361\362\312\300?\005}%`\211\'\204?mU<\207\310\020\274\277\353$\271aI5\260\277\253\254\333\027\242\026\277?\"\257\267\222\006\340\236\277\214\375\211\256\331\246\263\2773x\006\225\340\331\272?](\237\022\270P\310\277j\370\237/\330\321\303\277Z\247\220\376\207\275\320?WEQ\365\310\315\223\277J\340\027\205eo\325\277\366\344\340\324Q\266\274\277\317\030\241;\2158\271\277c\327\237\207D4\266\277Yx\210\263Ep\251?63z\376c\"\247\277\344\201\243\303L\251\264?\366\331\001W\330\270\327\277z\303Y\254\204\200\250\277^t6\025_\240\252\277\304\'S\177\263d\265?\203\204Q,6t\266\277\314\350\034\332\362(\273?C\353h\257\314\240\272?(\026\316\207,\030\317\277-PJ\034#\007\262?\222\247\026\303\251Z\240\277\231\016\032j^G\265\277\017i1\367\334\225\244?\023V\250\0368\270\233\277\271\361\234su\004\254\277\200\342\247B\304\227\264\277|\343&/\r\010\241\277|\243\246\260\035\204\224?\326\340\321o\030\017\300\277\200A)\231t\032\257?:SI0\271)\235?\232\224v\005qd|\277L!W\031\333\002\304?2/cm\'\221\247?\n5\253\010\376\242\255\277H\207\376%!\272\320?\035y>\340)\321\220\277!\242\252\036V.\267?0[~\230m\010\277?\357\217D!\332\007\242\277K\255\342\276Jl\246\277@|\024\001\256\326\252\277!\262\315\024\034\325\305\277\367\032<Y\303\216\227\277u\334CTs\343\313?\367;B\232:\314\321\277\363C5\343,R\301?wX\262\3052\346\263\277+ \202\037W\210\236\277\370\232x\247\366\357\247?\233RK\330\310\025\304?\"\"\265\2467\316q?\326\222\t\031\202\337\271\277\246\336\256\305\365\366\274\277\200\005\010\325\030\264\241?l\344k_\340\217\260\277\356p\001\204\023\351\246?\351\014\"<j\251\257?Zs\251\276j\220^?\013y6\210[&\266\277\274\265\nt:\315\327?I\320\204\200\3354\232\277\006\035\303\305\n\004w?,\372B5\271a\273\277p\241\337\273\367!\307\277\355\277\304\375\0024\224?\260c\3567Vy\273\277\347\255\020r9`\305?\267\247\316U\261\234\251\277\036\344%kD?\222??%79\353\227\321?^\252\327\313\347~\250\277\222\034\250\352,\211\213?\2323\341\276\327\265\210\277\000\005\363T\030\242\232\277Ud\345\333\2449\303\277\213Y-R\310\374\257?\311\017\362\020\365\"\236?L\277Eh8w\252?\007\360U`\002`\240\277\347O>\207\232\036\240?\262 j\032S\262\317?*^]\202!\371\310\277F\372\330\353\356\223|?\216\2319J\377M\264?\334k\351\210?9\242\277\306\246N\224\345\371\243?X\344\357\017\2350\266\277H\032\307\202\0005\262\277r\n\200\2113\236\257?\214aaG\255\271\271\277\003\037\265s-u\303?\245\342\373g?\310\236\277\317\377\352\221\274Z\231\277#\245v\235lb\265\277i\234\200_\033\377z\277[D)-\307:\236?\351\037\033\350\010+\264\277 pb3i\032\316?\034E\344\353%\360\207?\033C\002\013\300\376}\277~\005\313P\355\217\206?\275\2179s\320\371\301\277\217\024\344\230\273\323\221?\226\372U\324\347%\251\277\277~\3224\253`\227\277Ar\367h\236~\254?\310\201PA\375\035\232\277\037\243\253\204\335\344\273\277a\261\024\rN\245\272?\244\302lW\220\350\264\277\333kK{^i\242?7\326\003!aK\321?F2\177\")\255\226\277k\344\334\275\031\260\231?6\234bk\207\306\305\277zN\245zF\372\312\277\317r\017\342\207u\220\277\026-Y\272\246m\214\277<\005e\322F\010\261\277%;\247\036\222\217\242?1$K~\002\237.\277\211\204:T\305\327\265\277\250d\250\337\327T\315\277\226\231\306\204\322\203\300\277\321\003<k\tl\233\277\244K6f\374\223\307\277\216\243\301\235\301\363\246?\002\307\365\2401\206\304?Q\335}k@e\241?\315l\003\331\273,\272?\006\373I\250\034\257\301\277I6\374\321\255[\266?=\212+\260\024+\251?\000|\304\022\326\263\244?\250\021\241K\311\225\211\277\007\327\377\364x\036\227?\020\233Ku\203\355\277?\372}}\227x\271\273\277m\007\323e\006\016\264?\203\315?\277\016\236\253\277\341)T\215\354K\262\277\037\266\032\375>a\261\277M\336\375&\260\000\326?\313\226\205\267\341\364\250\277Bw\235hC\232\204?\371\375#$\371W\232?\356\034\223;\354\306\261\277\357\210_\323\272L\230?\277\354$P\037\212n?\274\036\225\t/([\277\204\033\221^t/\300\277\366\3708\220\020+\246\2772\\\376\237\242\037\253\277Z\342/,x\037\321?\342.&\371\336\210\245?\034\204\351\212\230\034\217\277\031\203m\2014\025\265\277\244\365\226G\214\376\246?\022t@\371\020\032\274\277\336b\033\307\003\010\271?=\337\366\243\n\361\302\277\310\261\t\t\"\210\255\277\024:\007\027\256V\247\277\351\352_\360\333-\277\277\333\227`\335\2433\310\277\273C;3]\233\244?nP|\300\234\351\206\277\310\304{Nqg\252?u\250\251\327\357N\205\277~\200E\030\232q\277?/<\020\0378\374\260\277i*\232\210n\026\302?_\324\021\240\003;\260\277\3636\233\323X\316\241\277\250\371\372E^\306\274?\243t\0078|\367\263\277~\216\352\206\031\306\265\277aY\223q\212\333\247\277\232\"\313\276\033,\232\2771\275\302L+\201\265?\203.r\034\226\277x?\263y\336\366\224\031\270?\353\364\320\364\035\375o?/\331\035\360\013\261\274?\321\233=<\244j\302\2773S\224\225q\342\334\277\221|\372\264\363g\264?\r\3604\270\222Z\264\277\306\227\020\206\014\016\227?t$+7A<\260?%|\370u\303\340\215\277\214\347y\225!h\300?\352{%g\"cB?\316-\322\n\223?\226\277$\024i\313\005+\240?\024\223b\031\324\006\274?\025\372\361C\"\336\256?\3163\366\377\222l\302\277\260\323\306{\246\367w\277\014y\'\223\270\337\265\277*\332\352D\227x\274?o@\267\235n\337\305\277$\217\\6\036\323}\277]\037\031\023\3221\246?\207\034\022\251\250w\275?n\353\260=\222d\237\2776\336b\303\010\036\322\277\237\220\312U\243\221\250?\"J\364\262\027:\301?I\335\276o\001\340\302?\351\245Y\323\027\032\277\277\205\233\361q\n\'\235\277\325\021\r\216\3627\256?\177ER\353#\376\275?\214\263\336P\332\331\244\277\235l\250\355\2128\220?\312\326\257A\330z\243?S\250\322rX\261\255?PWd\352\232\340\226?\271\'\004\300B\271R\277i\232\224FB\000\275?\264\014\307\343\320S\251?\256\246\242X\270A\213?\210\t\007\345*\377\301? k\326\234\323\241\267?\334P\314\373\377\302\266\277\301>\207#^\227\274\277,\2023\010>\203\301\277\341\372g\306Mc\251\277\372!\373\333\250K\250\277\374K\310d?\303\254\277\332\250S\017\177j\254?j\224\372d]\354\250?\364\313,\246&[\250?\356vv!\004\361\262\277A\2555nV\337\260\277\275\031\323#F\030\301\277\246K\310\260\r\000\222\277\250\313A_\'\344\255?\276}\034\362l\207\231?\342\261a\013\027i\261?v\215\227\317\252\206\256?T\355\026\002\234\032\266?Z\327\377P\2334\276\277)%\022b\t\233\266?7\241\177GQ4{\277\216\225\365Y[.h\277\334\337\212\376<@\300?\347\267\004\366\232\370\262\277\332\361gv\033\345\262\277\253M\202\325\200t\224?l\266LW\251\000\315\277J\213R\262\025\334\242?\212\323>\216\227\321\252\277\037\202|\354*]\250?y\317\036\rP\264\300\277\356\367\310\234g>\241\277\340~\214\006\342\345\264?}+0\255\200+\263?\330\007\235_\030\241\252?c\007\210\344\001)\274?;\374\334|\3006\267\277<zf\372\211\272\276?\203\325\031\235\223\257\236?\"\366\025\000\251\n\250\277^\271\207\002k\004\307?q\326\326\356l<\243?\277H,\222\013\020\254\277\314X`\321\036\237\277?\201v\353\004\316\261\316?\361\2527S\000\237p\277\242\300\240\0200\240\272?\235Yo\321\370\177\263\277H=\355\0107\327\246?\312GK\014=\032\236\277\036\034\272\353\000\351\266\277bF\262=\371\004\300?1\320\244mc\245\200?\357\252\347\3418\024\243?\344z~\342\007\313\226?XD\267l\035\267\214\277US|5 \244\257?\302\273\253\325\314D\255\277\n$\025\3279a\223?\323I\030W\013\246\205\277\362\240\211\n\223\301\242?e\350\033\337\'|\315\277\217\231\002\312\356Z\203\277{\357\210[1\032\246\2778~\014\\\177\213\222\277\236\243\212\340d\034\271\277\201B\252;{\221\277?\035\036\246\375P\033\265?!k\375( \026\306\277,\336\354JB\256\200?\222\232\212\271\351\337\276\277K>\350\326\021\027\270?\370\224\024\022L\245\313?y\374a\243B\343\246?\306\250h\227>\245\267\277\333]%\243q\255\260?Hi+\263\366%\260?+\023\324\325\014\247\242?\302|\364M\367\337\261?\242T\244\000\313\261\270\277\353\220\033C\235\005\246?\273\206Y\266\0377\262\277\277\315M\202\335\017\255?&\265P\353E\266\256?\351\315\352\277A\336\243\277k\334\217I\323\214\261\277\024_e\374\000E\271?\203&\230,[\336\266?\215\373\236.>\245\223\277\251\232\326\2646:\246\2777\252?\270R\r\224?\227M G\373\207\256?[F\273\036\305\236\220?\261\213 \246\351\t\301\277D\010oZ?\202\232?X\335 \020B\252\221?\245m\013\325\177E\322?\022\255t\276\2664\307?\240\310\251\315T\301\266\277J%s-}\243\326\277\361U\320?;\274\323?*L\3744R\223\237\277\337\331\377\222P\241\200\277\262@\372\242\377\256\251?\373\215~\036\020\316\202\277e\014\313\013\263\"\270\277A\262\301\374\363j\261?\315B\261)X\306\267\277F\333\260$\030W\224?\3538\272\212J\t\234\277J\366\247?\365c\224\2777\300\260Mf6\213?\347\"\021^s\245\307?f\325!\364\202\340\315\277HZ\3474S\026\266\277G+\214\271=\016\276?=\356Jl\243@\220\277E\262\374\205.n\242?\271\004\227Y\260\240t?Eh\371U\244P\271\277\257a.)\367\332\204\277\000\214\3158=p\267?;\251\230\256\350\240\234?s\354\315\370\r\177\232?@\214\"O\227O\313\277\310\354\250\236\303T\204\277Q\226\312\036\013\220\227?\361\257\036\250\025\356\201\277\303\211\211y9\316\227?\336\270K3\360\243\232?\330\013\035Z\256|\273?\360c\272\261-\t\260?\231c\273\202\231\027\253?\375\253\031\271\354\305\204\277\365\311$f$7\262\277\246c\004\"\211\013\310?Y\003M\227\340x\267?F\347\321\307\320\207\244?\032\023\035\330\243\312\262? m\032\360\266\001\261?\253\315\177\347\273{\263\277(\316\027\362H\326\275\277\352\362@1\316\363\227?\345\300\347\2205\337\245\277\271K\3441\'S\261?\241|%\036\337\313w?Y\270\235\305%A\270\277\224\313\266\211m\022\255\277\034.\201\3420\317\227\277K\274s\245u\t\273\277]$\001x\207\024\303\277B\300\315\371\020\344\277?\253\2453\202\t\306\262\277j\026\310\035\177\230\202?\253\243\017C\341\216\302?R\016\206\316\036\264\240\277\227\202\235eI6\240?\"\027\021\360>\377\243\277\373w\333\252\004\247\243?\312\007\0143B\315\274?\313\202\361\232Y\250\205?$\352\367\r\3667\233\277\241\t8\306o\375\272\277D\r\202\261\240\215\212\277=3\007\354\025\237\265?*\263\010Y\327\213\300?%;y\227\031\350\272\277\026\242\275\262\263!\253?\2124\222\026\314\233\257?Ns*,\031|\311?\305<\231\310=p+\277\316;_[\242f\251\277\231\005A\306\221?\233\277>\224>\330\004\350\306\277b\017\314\314\303\310\316?KO\220?Q\263\275?\231\271\330\301v\207\243\277\244s_\371\243\255\270?\244\321\013\000\2652\216\277\010)\014w\317\027\240?>\302\332r\314D\270?lx\320\270\277\223\264\277\'\n\320Pbw\224?\300F\331\335\001N\270?\177\215\202=R\377\261\277}\373^\323\004s\237\277\354\251(K\301I\307?=\331\001\243\036\236\325\277J\nh&\\Vt\277\273\007Z\352S\252\225\277\220\355~\2103\013\300?\313\370`\005\340`\310?\214\363\035\301\354A\205?\320)\276Wa\305\270?\317`D]#\270\277\277\220\372\035\263t$\234?]q\341l\2068|\277/\000\0204\351\023\326\277\000\311\300\231f\036\311?\366\277k\274z6\261?\212\257\255\234\370\231\204\277B\310~!\006wh\277D\274\255,\347\344\302\277\227\311)=\260\010\261\277\321^\337\357\354h\265\277\376\261D\365\027\255\273\277I\261BC\331\304\240\277\373C\335\370\033\364\307\277\026\242\223\344\370D\277\277\353\306\177\367\236v\225?\223\317\272\002\305\352\225?\362\003~\363C\233\200\277\305B;e\354\312\321?Y\n0oE0\244?\216\257\233Q\241\025\300?\215\2511\251\"}\231\277\2001\240\212\207\177\260?0\223\350\304\200\314\230\277\256Cf\021\222\355\177\277\200@m(\245\025\215\277\256\305N\247\342E\273\277m\343\031\366\374\272\256?:\030\342k\301\017\300\277\303/\360F\203\300\310?\243\335N\243Z\215\261\277`\356\007\357\261\360\314?\315\273\317\333\225\201\300?(\267\223\233\326\350\261?v\001\212\276\310\230\271?\274\233\007\204\237\266\322?t\3339\260\251>\251?\274\306\316$#\331\234?\265\214\225\333\300#\253?\252(\270\031\344n\261\277\002\203\340\212`\377\242\277\347eV4u0\254\277h\247\210\212\250\261\301\277\353\017\246S\332\313\261?D,\231\034f2w\277\376xy\340\003\375\276?\260V\022T1l\205\277\304\236o\252Q\363\244\277\235u\366\275\276\334\223\277\224\233Q\371\014\350\251\277\332\200\270mt\351\252?%\232\235\220],\253?z\236\362\376\277*\302\277\215J\314\341\277\275\277?\266L#[\212n\300?1]0\215\334O\255?\345\333Tg9M\213?\362V\205j\006\273\266?\'\215\354\360\253q\263\277\270N0\330(\035\250\2778\307\316*Rr\260\277S/\231r\264o\252\277C\356\\<\201\036~?\227\347!U\355L\245?\207c\371\234t\327\265\277\301\367\245\"\325\315\312?I\006\277\374\3060\274\277L:\244\364\275W\202?p\221\264\233h\264}?N\226\343H\253\017\263\277\230\212b\023\363\263\327\277\321\221\206\226\020b\226\2778\271u7\322\204\240\277{\312\311\035\272#\266\277\212\271;\235\266\367\325?\"\204d&\031x\225?]H\317\317z\373\266?M\217;\364\357\260\244?~\250\215q\036\334\224\277\323\307t\023\275\236\272\277\306\362I\262\327\037\277\277\320 \364*\305\276\261\277)7Q\222:\355\273?\320\262\277\331\303`\274?\373\234]BF\275\322\277\t\2745b\240F\252\2775d\371\022(J\253?\320\3032\2269\240\211?:\"u.\277\016\246?I\246J\021\2228\242?\\\010\256%<\247\256\277@\177\325}\361J\301?\'k\366J(n\251?V\211\341\227{\217\272?\303\366\007\361wF\307\277,N\"\0137\320\276?\027J\317\220\3550\260?\362h\315\270~&\266?\3608\303\244U<\327\277Xn\366\2530.\241\277\315\354\000\346\370 \222\277a\375\354C\272\250\270?&E\321\'=\304\275\277\211p4\223X\316\260\277\306\212\306\030ct\301?!V\026\274?\\\214?\243\021\215\306\325`\260?\177&v\020\372\355\215\277\3169\327Q\211+\260?\223\253-\317\271\353\300?\010\352\260C\374\235\242?\350\013\245\262\360\364\226\277\225\270\313\314\260t\224\277\233\0264\370\nU\236?\332y7\021d\315\250?\350?\316x\004\213\265\277_\005Rf\372s\216\2778\232\002<\210\354\255?w<t\311\304+\220?\224y\021\324\022F\316\277\230Y\tR\320\306\211?]\260\320\331.\034\245\277\003\001\320U\255yx?\324,\t\037X\212\244\277n\320\245g\"Y\265?\310\246*V\241\221\247\277,\205\312*\312\304\314\277dS\236\210Q[\260?\216\357\342\363b\375y?~\034\214\010I\205\246\277\330\016\305F\206W\320?GE\300\357\nI\247?\371\010\304&\r\222\257\277\310Z\252\373\204F\265?\036\013\263Y\320\t\273\277\324\177\301]\302\243\221\277M\213G\302\333\213\266?\312\024\201\031Q\247\267?\347G\373\227\210\267\242?^\247\344\207\341\010\211\277\376\267Y\262t\373\244\277^\343s\374v\270y\277\333%+<Q!\254\277\303\377\213\0202\002\220\277\325P\351\236\245Q\305\277S\260\263\246\n\353p?zQ\005u:\215\220?\225\256\301\262\356~\270?\322\375\035W\274\253\230?}\376D\2372\303\237?\227\007J\332\362\253\261?L>J\207\225R\301?JP\355-\317\215\253\277\205$\332\241\260\261\244?\222/\204\245\312\300\234\277O\273\355XJ\363\251\277\001Js\343\026\027\303\2775\321\301\245\237|\247\277D(\234\212\010\225\303\277ft\010M\3443\220\277\377D4w_\354\201\277\306\214\203\350b\251\274?\371\227\374\2711$\247\277O\237\231B\370\365\271?\362\r/\003ea\243?f\233\306\001\371\t\233?\r\257\202\267\333h\241?\327\226\223\373\207-\304?ZHo\010=v\301?,\0309\334\262\357\272\277\331\245\2523#q\264\277MG\276j\322\272\276??t\257\006&\212\200?\343-cD\274\375b\277\374\231\222\274\347\216\232\277\244A\250?\205\354\256?\346A\021\222\335~\302?\231+E\3376?\271\277\343A\270\252\177\370\275?\213\241K\325\207>\225?\005\363\264Z\377\353\233\277\371\023d\223\022e\261?\020\360]\360\270v\300\277\300\363\231\362\335c\203?\205\317\004\357;\364\243\277@\314\270\350(\030\260?\214\307\336,\327\326\273?ip0\334\323\225\274?H\244\231\013\0255\300\277\371w\332A\333\027\261\2777\221\\)y\233\263?\252\257\245\014w<\266?\251\373xQ] \232\277is\324o+\341\314?2v\305dQ\033\265\277\262c\223iIM\230\277U\034M\224\373\377\252\277)\325\255&av\206?\021[\026g\267\327\226?\213\227\347\331\346j\272\277Z\3146\250\347;\241\277\240!d%\2361\224?\005\354\371\177\364,\264?aT\207\016sV\263\277\306U\n\177x{\260\277\201\030\245R7\014\265\27742\254\014\231\253b\277\376\255\000{\037\333\254\277\246c\274\357\225\242\222?\303)\315\311\r\361\221\2774\213\237\311K\376\267?\317\354\177\325bl\202\277\364\nf\260\004\352\303\277\204\240\273\277W%\246\277\022\027\\\201\342\246\275?\026\035\r\016:\371\241?S\003\213N\353\264\253?\346\372\273\361\216]\302?\017\265\344\200\372\267\307\277q*X\037\362o\205\277:\310\026\007N\256\261\277(NZ@\367\002\231?\016\305`\0307\374\254?*\307b\276\361\373\246\277F\341@\001\350\274\267\277\302\256\010\204\261\321\273\277\356\205\255\365\363L\312?\300\263\361\342@\230\226?\003\337\254\321\254\267\312\277\267\246i(\220\276\271\277\031\241S\371=L\256?%\031=A\260C\262\277\030\364\010\214.H\263\277T)\026\177\2602\262\277\262\237\277s\314\022\262\277K\014\177\\t\025\305\277{\250\366\373\304\021\251\277|<i\3714\263\226\277\006\361T\370j\200\254\277\364\2103\376_5\253\277V\273G\005\305W\222\277g\201\002\301\314^\265?;\267\017#/(\305\277\010\361\342\025\'\330\304\277\034)\353,T\277\304\277\3526\234\023\306\037\313?\034\235\376\353\3611\217\277\260Ae*\262\027\260\277\035H\215\337\033\351\274\277\223\210\244\234J\024\250?\0373O,\326D\261\277\314\220\315`\301\004\301\277\n<tzZ\252\316\277\355\377\314\325b\276\264\277\266M\267Ew\252w?\017\307\234^\354k\303\277\024\006\221\177\344\310\332?\311\342\261+b\372\212?u\255\300\302\232a\221\277FEAw\347\356\254?\220\234\335\\\026\032\313\277\345\254P\224\230)\300\277`\330\367M\010\323\303\277\217\347~\214\322\371\266\277f\362\024\023\332k\254?\273\253\034e\021\320\277\277\212\031\033\306\023~\224?\255\000\023\035\203\340\211?Dh\221\351\252\352\251\277\263\241G3AK\265\277\303]5\246\\\315\246\277,\n\271Q\324Z\276?Rq\314\361K\271\237\277!\007YK\322s\302?\372\241\222e_\002\300\277\221\302\031\024\312#\242?\341cv\221K\007\265\277\242\230\275\363\233g\246?\343\324)?l\262\262?\033`\315\'\232}\236?l\013U\255\226\326\224?\002o\254/\2213\235\277D)_+\331\253\241\277&\202\271Pk\215\301?K\301\253\233\013\247\262\277\313\'\314s\364`\271?n\"\214\232\3227\302?mT\230\303\362W\202?\036\027 @\345\355\255\277\344\032:L\247\360\266\277\206zB:\343\245\263?\327I\346(\305\307\277\277\261\204\023T\276k\273?\206\033\322\331\331=\274?D\024\355\t\r\001\251?^\265\213<]\304\261?\351\r\211\241\221\370\263\277\222S/<\n \260?\275\365\317Pj\'\271?\263\265\r7>#\264\277\207)R\216\215\350\263?;\024\372:\356\346\254\277\375\006\023\364\014u\221\277\212\374Le\371p\204\277\3752(\033<q\241\277\026u\325\001QB\301?`q\335\202+0\267?\021n\024OSS\213?_\312\320EA\363\232?7\'\316\240iK\257\277\212G\232\322\240\277\304\277#v#\224 \264\265?\271\364\254=\240\214\265\277\316[a\256\320\032\247?\200\034i\374\244=\300?5r\027<Q\267\312?\020\200\242\017\3115\246?$\372\367\204\257e\223?\007\035O\361\350\251\260?\030\371\360\263\035\327\271?\303y%{\221\355\245?\030\212|\025*\222\215?f\3425\007+\245\250?\222|\0253\014A\226\277\325/\275w\323\205\260\277\212 \377W\370\001\261\277}a\336g\003R\275\2775\304\227}w]{\277\213:s\030\347$\307?\311A\030\336\305[\261\277g3\330\347\361\003\243\277{\353\215%\261\n\241?\276)\r\r\344\031\300?\272\226f\177\021}\266?\243\365]\342\002\003\245?\260\030<A\303\n\250?%n\331\236\225\256\270?q_\240-P\356\251\277\223h\316z\253\362\247\277c@\036\027\315y\225?+\313>b\302\324\247?\201yC\304\236\237\250\277\341W\323\016\022\224\263?A\300e\214s\277\302\277A\272\227\346\036/\216?\276t\343.An\242\2775eu\347n~\251?\274bN\177\331\366\310\277$1\245\033C\246\236?:NT\246\342o\254?\207\206J\240\370O\204?_\224)\222=\200\241\277K\035n;\020\222\262?Y\266#\234\313\003\207?\233\010$\326\2670\247\277U\222\256\211\316\360\240?X6dVa\031\221?D*/\tq\343\310?\255A\300\356\247\231\305\277=\324J\375\271\005\276\277\230!\312\205\346:\265?w,\331\212%\345r?.\373w\022\371\274\253\277\236\255_\223\204\337\264?k\025E\366\213|\313?\366\211\0169&f\222\277\240\344\376]\003\256\215?)[=\243\201S\222\277\227\312\376\036B\323\265\277eH3\\X\225\272\277\375ttX\265\350\237\277N\363\253\337\232\323\236?\t\377}\341\255\300\262\277\233j6\204\273\355\270?\250\233$<\r\344\244\277\301\242*\225\001\305\244\277\355@\025\0366P\212?\267\216%6\274}\263\277uu\353\340\232:\314?\316\000\245\017>\350N?\273\212\354\217\365\361\232\277\003\177\262\346\030\320\244\277\024J1\236\007\354\250?\237\246vFz%\231?\367\300[\341Q\311\260?\351\026\326\314\0175\246\277C\223\243u\332\245\274?T\264\311>\244\224a\277\212O\257|\0019\251\277\263\024\311\350|r\311?Y}\013\244H9\222?\273\343\301\354:_\301\277)\313z7\237\314\263\277f\246\237\306\356\013\264?\305X\303\030\325\257\250\277\3255<\3672;\313\277i\372X.\316\220\324?\016\332\214\333s\"\255?M\3174\235M\223\271?5\203\230\363\225\000\255\277\372\271\237\233Mw\242?\020\023\354\216\263ps\277\214\275^i\2279\271?J\017\237\376\2528\303?\237\243\312?\376\266\266\277?Ka\262;\301\216\277\351\275Ws\363\341\260\277\301\"\271\366\200v\244\277%q\2158\026\207\307?\325I\301{\'2\320\277Y\305\367\202\001\321\302\277\033\307\351\254\266\r\245?a\334J\346\255\243\263?\372\223\223t,\201\250\277\3764u\270X\005\256\277*\367-\265\000l\240\277\210\265\001~\244\272\211?\013\tqs\035\275\214?\244\0376dV\321\256?\261M\220\004_*\236\277\367\210A`\206d\272?\235\r\350l\250\317\250?\205\2001\345\266\245\301\277\034kt\247\212)\277\277\340\376`ix\313\201?N\205b/\013\200\265\2772\243\252\3162,\245?\231\235\350\236$\034\270?\022\002_\275\365\005\242?l\001\223\260h\352\256?\032\273\311\263\007N\247?{\\,\333\272o\211?\027{\370\223\240\221\232?\302^\213E\361\270\217\277%\007\325;\206\245\264?V7\242\204%a\253?\235\0369M\333r\306\2772n\232\237*\007\240?H\014\3360\322;\250?QRW\006\252\203\250?c2\014/\240\274I?\345\357~b\224\224\253\277e\032\021w\350a\315?<\307\332 \'\355\275?p\020\000{\003\326\272?\252\307\033}\252N\276?\000\225\311\350\343\300\241\277\274}$/\276E\220?\232\222\214Zx\250\271\277\222\220\215c\221\221\245?\004\035s\325\032\206\267\277j\310;\307\365\346\270\277\2444F\224*\365\322?\213\300\3548t\351\263\277\r\335\316\233\3712x?*\356\370\002\330\337\266?\342\016\213\334\267|\305\277\270\000+\242\334D\254?\264\000\226\007\201^\260?\025o\020\226\336F\321\277\000\247\r\026\2034\250\277[\007v\032oK\205?\014c\311\211\316\214\202?\275eN\033hP\240\277\213\226j*e`\223?\027\233\"\204N\006\237?6\314Z\357\020\265\321\277\340Xl\323\233_\234?\004\016d}\367}\261\277\327\243-\225G\270\237\2777)\341\023Ue\303?\022\t\030\241>+\264?\305\006\356\371\307\221\275\277l4\271P\231P\313?\036\336\370@0\217\247\277P|\244\370\226\310\252?\307\320[\372\342\274q\277\375\233T^\340\324\300?\253\353\227\363\367\314\303\277\276W\316\216\302\316\272?\3368\177\251\372K\274\277\335\350\364\001\277v\254\277p_3AjMe?\201\332\250*\364\256\327?\215\241\373\345k\337\260?)F\324\td\005\256?\347t\347\002\246P\264\277^XsY\207{\275\277P=dJ-G\261?D\236G\205l\231\273?\211&\253\212\'\377\310?HZ\321\341\205 \252\277\230E9\272\r\242\240?\276\207s\353n\203\301\277\024\305cz\215\366\262\277\361Q\366 @D\246\277\223Cw\343\366e\237?\313_a\277m\020\253?\306\266a\006\363z=\277\211\212\336+\226\027\307\277\315\363]&\2334\316?\203\251qq\246\033\315\277\203o\rb\276\324\235\277\357\213\264\270\237\024\277?\316\257\312\303J\315X\277\024=\003\245S\233\271?\227\314x\243\222\273\303\277m\010\202\216\351\352\237?X\314\226]~\264\306?\313\245:\272c\373_\277\005\301\261\327lR\301\277\275i^d\234r\310?\010 \350\300\344\361\256\277i\036\230\364l\252\223?d;\201@\271\354\257\277;\342@\202\2402\251?\306\353\215m\376\251\227\277\251\304\005\271v\274\214\277\241I\005<\330\224\225\277\255\205L\2377\013\247\277\021\360\\2\221|\264?{&\253\032\275\\\306?W\250\355(J\344\313\277,e\376\226L`\223\277\017:\303E\304\010\202\277X\263\321\230;\363\243\277\335\364\004\271\210\367\250?\227i\331\334\026\033@\277\352\341\353\0315\351\266?]\276\"\232\361s\226\277:\342\376\352\007\207\261\277\277\313\314\205\244^\247\277c\006\2056q.\261\277]U\375\251\004\303\201\277\016+\317\371\371\014\264\277\004k\371X\216\031\262?\327\007\261>)\361\327?v\341\312da\276\271?\321q-Nn]\222\277 D\037\021\232\365\321\277\005\231C\244\317\356\264?\270~\247fG\334\300?S|S\277\206\341\276\277\254z\237\355\n\031\243\277\376q\014\361\200]\240\277\007?L!\272\257\260\277\243J\261/\023p\275?3\017\371\226l\325\241?\306\355\362h\036d\251\277\245\276zj\2435\251?\260E\260\330\216\006y?\205p\0354\201O\305\277\242\266g\303\311\322\266?\326\206:n\275\000\213\277\221\306mJ#\337\235\277\210-\373b\270+\267?\325s`\336\342\266\274?\340\326Qe\353Q\304\277\030\242m:\371w\262\277\021\367\210\356\333\014\233?t\327\265\263\270\247\301?\364`MK\035Q\252\277\340\353q\221\246_\256?\236\010!\001\224\202\266\277B/(\366\305E\273\277i\320\253\027}\243\261?rUV\352\224m\245?\035\270\013\241Z\t\245?\226;\321\202$\014\250\277\322!\235`,\372\240?u}=\220T\342\177?>\355\215\341X\000\230\277\370\351F\n\377d\272?\364\266PN\310\332\240\277m\230J\272\346?\307?vJ5l\212\200\301\277\201$\206\223\344\260\260?m\210\276\230\273x\321\277+p\333\003\332d\307\277\252[\273o\000\227\247?\306\361\323v\305\245\246\277\026c\336JO<\200\277m\370\0105\036T\200?s<\300\247\330D\261\277\237h\277\"\336\200l?]$\263p\377\214\222?-\032\025\221\036\256\256\277,^[\032u\353\250?\310U\220dR\340\260?\225\267\322\202\205\205\240?2ro\344\265\331X\277\216xr\347\341k\262\277\310QG\032\027k\253?\023\300$\222(M\265?p?\016\203\026\373\243\277\020h[\030#\335\261?\331K\374r\317\271\262\277y\257+\327\341j\270\277\214\014\257.\020t\267\277c\213\0348JI\321?^\316\231\000\253\366\203\277\314\210\272\257 e\221\277\361\362\302\014wl\245\277\030\324\365\020Q)\236\277\360\206&\356\261j\236?\357f\006\236\314\007\304?\254\330KC\315\347\254\277\356\252\345 \241#\244\277\255tT\236+,\205?v\210\221\300\025\'\272\277\325\331~\330C\005r\277\235N\200\207\321\265\267\277\330\336\323\356\206\276\256\277\0255\227)\\H\301\277X\211\033&\017\277\263?\371W\375\035\354e\212\277\277\327\363\177\254m\214\277\214?C\252\020g\255\277\314(\236v_\351\212?\247\235\023\241\320\024\270?:\357&p\3730\265?\025\325d\026#\276\220?\035\254\331\351\\\355\274?D\303\032M\003\375\265\277\031\250\'aV\275\312?GF\263.@\216\236?V\223\317X{\372z?m\275\tJQ\276\300\277\376\334R%\037\222\272?$b\333c\307\032\254?\370\337\2430\r\246\273?\371\367*\313\267L\246\277\000@\235\211\327]\224\277\2624\355\337?\274\223?\321_)\355\213\313\314?M\310t\344\245P\252?9gM\310L,\304\277\031\tP\"\016\225\251\2772\323\316{\332\322\265?0\267!\273\225\370\222?\244\343\227_%\351\261?\037\'\344\313\352>\314?\022\024G\205\2736\273\277\233\000\360\240QE\222?\303\033W\210\357\260\201?\243\r2}\n\004\245?\203K\352\217\177\364\277\277\352\025\232\r\342N\311\277d\317\004\330\311\'\301\277R~&W\2020\263\2777YK\352Fi\241?\322\277\310i\254\206\261?a\311\372\313\206\266\261?a\327\3354;\013\205?n\264\006\261+\023\227?\270\227a\241\224\246\273?\302\200\323\010\357\253\244\277\364D\374(t\016\207?\003H\301\"\031\215\266?1\374H@\377f\252?5\276#\234\216v\272\277\255 \320\217X\301\241\277\202\231\005\035\035\242\301\277!\364=t\276\201\253?\214?\210\361\tU\263?\273\017\353\343\213$\300?IqQ\371\367$\304?\315\202\361\017\240\021\264?\371*\264?y\037\231?G\356V\005\310\222\233\277\261|\331\253Io\247\277\334<\023\367$\037\242\277\263W\237\367\356\376\300\277\264RA\225\037@\227?\367a+0?\204\204?\246\333\266\214tL\306?Np\342\247\356\021\274\277\240\305\003\201\004\362\177\277cn\353\n\3104\254?\370r\rt\026_\270?]+BF u\302?\324\237[m\256\331\231\277" + } + } + } + } + node { + name: "Const_1" + op: "Const" + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: 14 + } + } + } + } + } + attr { + key: "dtype" + value { + type: DT_DOUBLE + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_DOUBLE + tensor_shape { + dim { + size: 14 + } + } + tensor_content: "\247X\347\2675\264\217\2776y\323\323 \301\226\277\214\261\027Ns\000\225\2772\204\nm\231\232\237\277\306i\204\000\242\315\231\277\245D\217\003\3660\226\277\021\365B\360P\003\226?\331X2\327\340\225\235\277#\335\372\000|\000\242\277p\370\027\263\343\200\227\277\2660{\320?\205\221\277\233\341\253q5<\246\277\357\211\251\233\2604\242\277\300\245M\305}\017\241\277" + } + } + } + } + node { + name: "MatMul" + op: "MatMul" + input: "input" + input: "Const" + attr { + key: "T" + value { + type: DT_DOUBLE + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + attr { + key: "transpose_a" + value { + b: false + } + } + attr { + key: "transpose_b" + value { + b: false + } + } + } + node { + name: "add" + op: "Add" + input: "MatMul" + input: "Const_1" + attr { + key: "T" + value { + type: DT_DOUBLE + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + node { + name: "predict" + op: "Identity" + input: "add" + attr { + key: "T" + value { + type: DT_DOUBLE + } + } + attr { + key: "_output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + } + } + versions { + producer: 27 + } + } + signature_def { + key: "serving_default" + value { + inputs { + key: "input" + value { + name: "input:0" + dtype: DT_DOUBLE + tensor_shape { + dim { + size: -1 + } + dim { + size: 1536 + } + } + } + } + outputs { + key: "output" + value { + name: "predict:0" + dtype: DT_DOUBLE + tensor_shape { + dim { + size: -1 + } + dim { + size: 14 + } + } + } + } + method_name: "tensorflow/serving/predict" + } + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java index 4abc4182dd5..aa537d4f69a 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java @@ -109,6 +109,15 @@ public class DockerOperationsImpl implements DockerOperations { addMounts(context, command); + // TODO: Enforce disk constraints + long minMainMemoryAvailableMb = (long) (context.node().memoryGb() * 1024); + if (minMainMemoryAvailableMb > 0) { + // VESPA_TOTAL_MEMORY_MB is used to make any jdisc container think the machine + // only has this much physical memory (overrides total memory reported by `free -m`). + // TODO: Remove after all tenants are running > 7.67 + command.withEnvironment("VESPA_TOTAL_MEMORY_MB", Long.toString(minMainMemoryAvailableMb)); + } + logger.info("Creating new container with args: " + command); command.create(); } @@ -276,6 +285,7 @@ public class DockerOperationsImpl implements DockerOperations { context.pathInNodeUnderVespaHome("var/db/vespa"), context.pathInNodeUnderVespaHome("var/jdisc_container"), context.pathInNodeUnderVespaHome("var/mediasearch"), // TODO: Remove when Vespa 6 is gone + context.pathInNodeUnderVespaHome("var/run"), // TODO: Remove - contains .pid files context.pathInNodeUnderVespaHome("var/vespa"), context.pathInNodeUnderVespaHome("var/yca"), context.pathInNodeUnderVespaHome("var/zookeeper") // Tenant content nodes, config server and controller diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java index 167ca15bdbf..f4355ed3afa 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java @@ -86,7 +86,7 @@ public class StorageMaintainer { throw new RuntimeException("Result from disk usage command not as expected: " + output); } - return 1024 * Long.valueOf(results[0]); + return 1024 * Long.parseLong(results[0]); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java index 2cddee6aa2a..6e5f65a5d48 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java @@ -77,10 +77,23 @@ public class CoreCollector { List<String> readBacktrace(NodeAgentContext context, Path coredumpPath, Path binPath, boolean allThreads) { String threads = allThreads ? "thread apply all bt" : "bt"; String[] command = {gdb.toString(), "-n", "-ex", threads, "-batch", binPath.toString(), coredumpPath.toString()}; + ProcessResult result = docker.executeCommandInContainerAsRoot(context, command); - if (result.getExitStatus() != 0) { + if (result.getExitStatus() != 0) throw new RuntimeException("Failed to read backtrace " + result + ", Command: " + Arrays.toString(command)); - } + + return Arrays.asList(result.getOutput().split("\n")); + } + + List<String> readJstack(NodeAgentContext context, Path coredumpPath, Path binPath) { + String[] command = isRunningVespa6(context) ? + new String[] {"jstack", binPath.toString(), coredumpPath.toString()} : + new String[] {"jhsdb", "jstack", "--exe", binPath.toString(), "--core", coredumpPath.toString()}; + + ProcessResult result = docker.executeCommandInContainerAsRoot(context, command); + if (result.getExitStatus() != 0) + throw new RuntimeException("Failed to read jstack " + result + ", Command: " + Arrays.toString(command)); + return Arrays.asList(result.getOutput().split("\n")); } @@ -96,11 +109,19 @@ public class CoreCollector { Path binPath = readBinPath(context, coredumpPath); data.put("bin_path", binPath.toString()); - data.put("backtrace", readBacktrace(context, coredumpPath, binPath, false)); - data.put("backtrace_all_threads", readBacktrace(context, coredumpPath, binPath, true)); + if (binPath.getFileName().toString().equals("java")) { + data.put("backtrace_all_threads", readJstack(context, coredumpPath, binPath)); + } else { + data.put("backtrace", readBacktrace(context, coredumpPath, binPath, false)); + data.put("backtrace_all_threads", readBacktrace(context, coredumpPath, binPath, true)); + } } catch (RuntimeException e) { context.log(logger, Level.WARNING, "Failed to extract backtrace", e); } return data; } + + private static boolean isRunningVespa6(NodeAgentContext context) { + return context.node().wantedVespaVersion().map(v -> v.getMajor() == 6).orElse(false); + } } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java index ef8ea60bee3..e20480e14ef 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java @@ -18,6 +18,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -183,8 +184,8 @@ public class NodeAgentContextImpl implements NodeAgentContext { .flavor("d-2-8-50"); } - public Builder nodeType(NodeType nodeType) { - this.nodeSpecBuilder.type(nodeType); + public Builder nodeSpecBuilder(Function<NodeSpec.Builder, NodeSpec.Builder> nodeSpecBuilderModifier) { + this.nodeSpecBuilder = nodeSpecBuilderModifier.apply(nodeSpecBuilder); return this; } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java index 77c08133e82..b7e7b97cdd8 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java @@ -355,8 +355,7 @@ public class NodeAgentImpl implements NodeAgent { } private boolean noCpuCap(ZoneApi zone) { - return zone.getEnvironment() == Environment.dev - || (zone.getSystemName().isCd() && zone.getEnvironment() != Environment.prod); + return zone.getEnvironment() == Environment.dev || zone.getSystemName().isCd(); } private boolean downloadImageIfNeeded(NodeSpec node, Optional<Container> container) { diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java index a815515ac83..9151dde19a6 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/StoredInteger.java @@ -34,7 +34,7 @@ public class StoredInteger implements Supplier<OptionalInt> { if (!hasBeenRead) { try { String value = new String(Files.readAllBytes(path)); - this.value = OptionalInt.of(Integer.valueOf(value)); + this.value = OptionalInt.of(Integer.parseInt(value)); } catch (NoSuchFileException e) { this.value = OptionalInt.empty(); } catch (IOException e) { diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java index d809d9cbf96..37156ade064 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.maintenance.coredump; +import com.yahoo.component.Version; import com.yahoo.vespa.hosted.dockerapi.ProcessResult; import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; @@ -9,9 +10,6 @@ import org.junit.Test; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,13 +23,14 @@ import static org.mockito.Mockito.when; */ public class CoreCollectorTest { private final String GDB_PATH = "/my/path/to/gdb"; + private final String JDK_PATH = "/path/to/jdk/java"; private final DockerOperations docker = mock(DockerOperations.class); private final CoreCollector coreCollector = new CoreCollector(docker, Paths.get(GDB_PATH)); private final NodeAgentContext context = new NodeAgentContextImpl.Builder("container-123.domain.tld").build(); private final Path TEST_CORE_PATH = Paths.get("/tmp/core.1234"); private final Path TEST_BIN_PATH = Paths.get("/usr/bin/program"); - private final List<String> GDB_BACKTRACE = Arrays.asList("[New Thread 2703]", + private final List<String> GDB_BACKTRACE = List.of("[New Thread 2703]", "Core was generated by `/usr/bin/program\'.", "Program terminated with signal 11, Segmentation fault.", "#0 0x00000000004004d8 in main (argv=0x1) at main.c:4", "4\t printf(argv[3]);", "#0 0x00000000004004d8 in main (argv=0x1) at main.c:4"); @@ -127,10 +126,10 @@ public class CoreCollectorTest { "/usr/bin/program", "/tmp/core.1234"}, String.join("\n", GDB_BACKTRACE)); - Map<String, Object> expectedData = new HashMap<>(); - expectedData.put("bin_path", TEST_BIN_PATH.toString()); - expectedData.put("backtrace", new ArrayList<>(GDB_BACKTRACE)); - expectedData.put("backtrace_all_threads", new ArrayList<>(GDB_BACKTRACE)); + Map<String, Object> expectedData = Map.of( + "bin_path", TEST_BIN_PATH.toString(), + "backtrace", GDB_BACKTRACE, + "backtrace_all_threads", GDB_BACKTRACE); assertEquals(expectedData, coreCollector.collect(context, TEST_CORE_PATH)); } @@ -142,16 +141,57 @@ public class CoreCollectorTest { mockExec(new String[]{GDB_PATH + " -n -ex bt -batch /usr/bin/program /tmp/core.1234"}, "", "Failure"); - Map<String, Object> expectedData = new HashMap<>(); - expectedData.put("bin_path", TEST_BIN_PATH.toString()); + Map<String, Object> expectedData = Map.of("bin_path", TEST_BIN_PATH.toString()); assertEquals(expectedData, coreCollector.collect(context, TEST_CORE_PATH)); } + @Test + public void reportsJstackInsteadOfGdbForJdkCores() { + mockExec(new String[]{"file", TEST_CORE_PATH.toString()}, + "dump.core.5954: ELF 64-bit LSB core file x86-64, version 1 (SYSV), too many program header sections (33172)"); + mockExec(new String[]{"/bin/sh", "-c", GDB_PATH + " -n -batch -core /tmp/core.1234 | grep '^Core was generated by'"}, + "Core was generated by `" + JDK_PATH + " -Dconfig.id=default/container.11 -XX:+Pre'."); + + String jstack = "jstack11"; + mockExec(new String[]{"jhsdb", "jstack", "--exe", JDK_PATH, "--core", "/tmp/core.1234"}, + jstack); + + Map<String, Object> expectedData = Map.of( + "bin_path", JDK_PATH, + "backtrace_all_threads", List.of(jstack)); + assertEquals(expectedData, coreCollector.collect(context, TEST_CORE_PATH)); + } + + @Test + public void reportsJstackInsteadOfGdbForJdkCoresVespa6() { + NodeAgentContext contextVespa6 = new NodeAgentContextImpl.Builder("container-123.domain.tld") + .nodeSpecBuilder(n -> n.wantedVespaVersion(Version.fromString("6.330.51"))) + .build(); + + mockExec(contextVespa6, new String[]{"file", TEST_CORE_PATH.toString()}, + "dump.core.5954: ELF 64-bit LSB core file x86-64, version 1 (SYSV), too many program header sections (33172)", ""); + mockExec(contextVespa6, new String[]{"/bin/sh", "-c", GDB_PATH + " -n -batch -core /tmp/core.1234 | grep '^Core was generated by'"}, + "Core was generated by `" + JDK_PATH + " -Dconfig.id=default/container.11 -XX:+Pre'.", ""); + + String jstack = "jstack8"; + mockExec(contextVespa6, new String[]{"jstack", JDK_PATH, "/tmp/core.1234"}, + jstack, ""); + + Map<String, Object> expectedData = Map.of( + "bin_path", JDK_PATH, + "backtrace_all_threads", List.of(jstack)); + assertEquals(expectedData, coreCollector.collect(contextVespa6, TEST_CORE_PATH)); + } + private void mockExec(String[] cmd, String output) { mockExec(cmd, output, ""); } private void mockExec(String[] cmd, String output, String error) { + mockExec(context, cmd, output, error); + } + + private void mockExec(NodeAgentContext context, String[] cmd, String output, String error) { when(docker.executeCommandInContainerAsRoot(context, cmd)) .thenReturn(new ProcessResult(error.isEmpty() ? 0 : 1, output, error)); } diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java index ca9b05a3ff6..b33f52ff629 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminImplTest.java @@ -1,11 +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.hosted.node.admin.nodeadmin; -import com.yahoo.config.provision.NodeType; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.dockerapi.metrics.Metrics; -import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec; -import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeState; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl; import org.junit.Test; @@ -157,14 +154,7 @@ public class NodeAdminImplTest { } private NodeAgentContext createNodeAgentContext(String hostname) { - NodeSpec nodeSpec = new NodeSpec.Builder() - .hostname(hostname) - .state(NodeState.active) - .type(NodeType.tenant) - .flavor("default") - .build(); - - return new NodeAgentContextImpl.Builder(nodeSpec).build(); + return new NodeAgentContextImpl.Builder(hostname).build(); } private NodeAgentWithScheduler mockNodeAgentWithSchedulerFactory(NodeAgentContext context) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java new file mode 100644 index 00000000000..48f846d5e7f --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java @@ -0,0 +1,526 @@ +package com.yahoo.vespa.hosted.provision.maintenance; + +import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.NodeType; +import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.node.Allocation; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class CapacityChecker { + private List<Node> hosts; + Map<String, Node> nodeMap; + private Map<Node, List<Node>> nodeChildren; + private Map<Node, AllocationResources> availableResources; + + public AllocationHistory allocationHistory = null; + + public CapacityChecker(NodeRepository nodeRepository) { + this.hosts = getHosts(nodeRepository); + List<Node> tenants = getTenants(nodeRepository, hosts); + nodeMap = constructHostnameToNodeMap(hosts); + this.nodeChildren = constructNodeChildrenMap(tenants, hosts, nodeMap); + this.availableResources = constructAvailableResourcesMap(hosts, nodeChildren); + } + + public List<Node> getHosts() { + return hosts; + } + + public Optional<HostFailurePath> worstCaseHostLossLeadingToFailure() { + Map<Node, Integer> timesNodeCanBeRemoved = computeMaximalRepeatedRemovals(hosts, nodeChildren, availableResources); + return greedyHeuristicFindFailurePath(timesNodeCanBeRemoved, hosts, nodeChildren, availableResources); + } + + protected List<Node> findOvercommittedHosts() { + return findOvercommittedNodes(availableResources); + } + + public List<Node> nodesFromHostnames(List<String> hostnames) { + List<Node> nodes = hostnames.stream() + .filter(h -> nodeMap.containsKey(h)) + .map(h -> nodeMap.get(h)) + .collect(Collectors.toList()); + if (nodes.size() != hostnames.size()) { + Set<String> notFoundNodes = new HashSet<>(hostnames); + notFoundNodes.removeAll(nodes.stream().map(Node::hostname).collect(Collectors.toList())); + throw new IllegalArgumentException(String.format("Host(s) not found: [ %s ]", + String.join(", ", notFoundNodes))); + } + + return nodes; + } + + public Optional<HostFailurePath> findHostRemovalFailure(List<Node> hostsToRemove) { + var removal = findHostRemovalFailure(hostsToRemove, hosts, nodeChildren, availableResources); + if (removal.isEmpty()) return Optional.empty(); + HostFailurePath failurePath = new HostFailurePath(); + failurePath.hostsCausingFailure = hostsToRemove; + failurePath.failureReason = removal.get(); + return Optional.of(failurePath); + } + + // We only care about nodes in one of these states. + private static Node.State[] relevantNodeStates = { + Node.State.active, + Node.State.inactive, + Node.State.dirty, + Node.State.provisioned, + Node.State.ready, + Node.State.reserved + }; + + private List<Node> getHosts(NodeRepository nodeRepository) { + return nodeRepository.getNodes(NodeType.host, relevantNodeStates); + } + + private List<Node> getTenants(NodeRepository nodeRepository, List<Node> hosts) { + var parentNames = hosts.stream().map(Node::hostname).collect(Collectors.toSet()); + return nodeRepository.getNodes(NodeType.tenant, relevantNodeStates).stream() + .filter(t -> parentNames.contains(t.parentHostname().orElse(""))) + .collect(Collectors.toList()); + } + + private Optional<HostFailurePath> greedyHeuristicFindFailurePath(Map<Node, Integer> heuristic, List<Node> hosts, + Map<Node, List<Node>> nodeChildren, + Map<Node, AllocationResources> availableResources) { + if (hosts.size() == 0) return Optional.empty(); + + List<Node> parentRemovalPriorityList = heuristic.entrySet().stream() + .sorted(Comparator.comparingInt(Map.Entry::getValue)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + for (int i = 1; i <= parentRemovalPriorityList.size(); i++) { + List<Node> hostsToRemove = parentRemovalPriorityList.subList(0, i); + var hostRemovalFailure = findHostRemovalFailure(hostsToRemove, hosts, nodeChildren, availableResources); + if (hostRemovalFailure.isPresent()) { + HostFailurePath failurePath = new HostFailurePath(); + failurePath.hostsCausingFailure = hostsToRemove; + failurePath.failureReason = hostRemovalFailure.get(); + return Optional.of(failurePath); + } + } + + throw new IllegalStateException("No path to failure found. This should be impossible!"); + } + + private Map<String, Node> constructHostnameToNodeMap(List<Node> nodes) { + return nodes.stream().collect(Collectors.toMap(Node::hostname, n -> n)); + } + + private Map<Node, List<Node>> constructNodeChildrenMap(List<Node> tenants, List<Node> hosts, Map<String, Node> hostnameToNode) { + Map<Node, List<Node>> nodeChildren = tenants.stream() + .filter(n -> n.parentHostname().isPresent()) + .filter(n -> hostnameToNode.containsKey(n.parentHostname().get())) + .collect(Collectors.groupingBy( + n -> hostnameToNode.get(n.parentHostname().orElseThrow()))); + + for (var host : hosts) nodeChildren.putIfAbsent(host, List.of()); + + return nodeChildren; + } + + private Map<Node, AllocationResources> constructAvailableResourcesMap(List<Node> hosts, Map<Node, List<Node>> nodeChildren) { + Map<Node, AllocationResources> availableResources = new HashMap<>(); + for (var host : hosts) { + NodeResources hostResources = host.flavor().resources(); + int occupiedIps = 0; + Set<String> ipPool = host.ipAddressPool().asSet(); + for (var child : nodeChildren.get(host)) { + hostResources = hostResources.subtract(child.flavor().resources().withDiskSpeed(NodeResources.DiskSpeed.any)); + occupiedIps += child.ipAddresses().stream().filter(ipPool::contains).count(); + } + availableResources.put(host, new AllocationResources(hostResources, host.ipAddressPool().asSet().size() - occupiedIps)); + } + + return availableResources; + } + + /** + * Computes a heuristic for each host, with a lower score indicating a higher perceived likelihood that removing + * the host causes an unrecoverable state + */ + private Map<Node, Integer> computeMaximalRepeatedRemovals(List<Node> hosts, Map<Node, List<Node>> nodeChildren, + Map<Node, AllocationResources> availableResources) { + Map<Node, Integer> timesNodeCanBeRemoved = hosts.stream().collect(Collectors.toMap( + Function.identity(), + _x -> Integer.MAX_VALUE + )); + for (Node host : hosts) { + List<Node> children = nodeChildren.get(host); + if (children.size() == 0) continue; + Map<Node, AllocationResources> resourceMap = new HashMap<>(availableResources); + Map<Node, List<Allocation>> containedAllocations = collateAllocations(nodeChildren); + + int timesHostCanBeRemoved = 0; + Optional<Node> unallocatedNode; + while (timesHostCanBeRemoved < 1000) { // Arbritrary upper bound + unallocatedNode = tryAllocateNodes(nodeChildren.get(host), hosts, resourceMap, containedAllocations); + if (unallocatedNode.isEmpty()) { + timesHostCanBeRemoved++; + } else break; + } + timesNodeCanBeRemoved.put(host, timesHostCanBeRemoved); + } + + return timesNodeCanBeRemoved; + } + + private List<Node> findOvercommittedNodes(Map<Node, AllocationResources> availableResources) { + List<Node> overcommittedNodes = new ArrayList<>(); + for (var entry : availableResources.entrySet()) { + var resources = entry.getValue().nodeResources; + if (resources.vcpu() < 0 || resources.memoryGb() < 0 || resources.diskGb() < 0) { + overcommittedNodes.add(entry.getKey()); + } + } + return overcommittedNodes; + } + + private Map<Node, List<Allocation>> collateAllocations(Map<Node, List<Node>> nodeChildren) { + return nodeChildren.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue().stream() + .map(Node::allocation).flatMap(Optional::stream) + .collect(Collectors.toList()) + )); + } + + /** + * Tests whether it's possible to remove the provided hosts. + * Does not mutate any input variable. + * @return Empty optional if removal is possible, information on what caused the failure otherwise + */ + private Optional<HostRemovalFailure> findHostRemovalFailure(List<Node> hostsToRemove, List<Node> allHosts, + Map<Node, List<Node>> nodechildren, + Map<Node, AllocationResources> availableResources) { + var containedAllocations = collateAllocations(nodechildren); + var resourceMap = new HashMap<>(availableResources); + List<Node> validAllocationTargets = allHosts.stream() + .filter(h -> !hostsToRemove.contains(h)) + .collect(Collectors.toList()); + if (validAllocationTargets.size() == 0) { + return Optional.of(HostRemovalFailure.none()); + } + + allocationHistory = new AllocationHistory(); + for (var host : hostsToRemove) { + Optional<Node> unallocatedNode = tryAllocateNodes(nodechildren.get(host), + validAllocationTargets, resourceMap, containedAllocations, true); + + if (unallocatedNode.isPresent()) { + AllocationFailureReasonList failures = collateAllocationFailures(unallocatedNode.get(), + validAllocationTargets, resourceMap, containedAllocations); + return Optional.of(HostRemovalFailure.create(host, unallocatedNode.get(), failures)); + } + } + return Optional.empty(); + } + + /** + * Attempts to allocate the listed nodes to a new host, mutating availableResources and containedAllocations, + * optionally returning the first node to fail, if one does. + * */ + private Optional<Node> tryAllocateNodes(List<Node> nodes, List<Node> hosts, + Map<Node, AllocationResources> availableResources, + Map<Node, List<Allocation>> containedAllocations) { + return tryAllocateNodes(nodes, hosts, availableResources, containedAllocations, false); + } + private Optional<Node> tryAllocateNodes(List<Node> nodes, List<Node> hosts, + Map<Node, AllocationResources> availableResources, + Map<Node, List<Allocation>> containedAllocations, boolean withHistory) { + for (var node : nodes) { + var newParent = tryAllocateNode(node, hosts, availableResources, containedAllocations); + if (newParent.isEmpty()) { + if (withHistory) allocationHistory.addEntry(node, null, 0); + return Optional.of(node); + } + if (withHistory) { + long eligibleParents = + hosts.stream().filter(h -> + !violatesParentHostPolicy(node, h, containedAllocations) + && availableResources.get(h).satisfies(AllocationResources.from(node.flavor().resources()))).count(); + allocationHistory.addEntry(node, newParent.get(), eligibleParents + 1); + } + } + return Optional.empty(); + } + + /** + * @return The parent to which the node was allocated, if it was successfully allocated. + */ + private Optional<Node> tryAllocateNode(Node node, List<Node> hosts, + Map<Node, AllocationResources> availableResources, + Map<Node, List<Allocation>> containedAllocations) { + AllocationResources requiredNodeResources = AllocationResources.from(node.flavor().resources()); + for (var host : hosts) { + var availableHostResources = availableResources.get(host); + if (violatesParentHostPolicy(node, host, containedAllocations)) { + continue; + } + if (availableHostResources.satisfies(requiredNodeResources)) { + availableResources.put(host, availableHostResources.subtract(requiredNodeResources)); + if (node.allocation().isPresent()) { + containedAllocations.get(host).add(node.allocation().get()); + } + return Optional.of(host); + } + } + + return Optional.empty(); + } + + private static boolean violatesParentHostPolicy(Node node, Node host, Map<Node, List<Allocation>> containedAllocations) { + if (node.allocation().isEmpty()) return false; + Allocation nodeAllocation = node.allocation().get(); + for (var allocation : containedAllocations.get(host)) { + if (allocation.membership().cluster().equalsIgnoringGroupAndVespaVersion(nodeAllocation.membership().cluster()) + && allocation.owner().equals(nodeAllocation.owner())) { + return true; + } + } + return false; + } + + private AllocationFailureReasonList collateAllocationFailures(Node node, List<Node> hosts, + Map<Node, AllocationResources> availableResources, + Map<Node, List<Allocation>> containedAllocations) { + List<AllocationFailureReason> allocationFailureReasons = new ArrayList<>(); + for (var host : hosts) { + AllocationFailureReason reason = new AllocationFailureReason(host); + var availableHostResources = availableResources.get(host); + reason.violatesParentHostPolicy = violatesParentHostPolicy(node, host, containedAllocations); + + NodeResources l = availableHostResources.nodeResources; + NodeResources r = node.flavor().resources(); + if (l.vcpu() < r.vcpu()) { reason.insufficientVcpu = true; } + if (l.memoryGb() < r.memoryGb()) { reason.insufficientMemoryGb = true; } + if (l.diskGb() < r.diskGb()) { reason.insufficientDiskGb = true; } + if (r.diskSpeed() != NodeResources.DiskSpeed.any && r.diskSpeed() != l.diskSpeed()) + { reason.incompatibleDiskSpeed = true; } + if (availableHostResources.availableIPs < 1) { reason.insufficientAvailableIPs = true; } + + allocationFailureReasons.add(reason); + } + + return new AllocationFailureReasonList(allocationFailureReasons); + } + + /** + * Contains the list of hosts that, upon being removed, caused an unrecoverable state, + * as well as the specific host and tenant which caused it. + */ + public static class HostFailurePath { + public List<Node> hostsCausingFailure; + public HostRemovalFailure failureReason; + } + + /** + * Data class used for detailing why removing the given tenant from the given host was unsuccessful. + * A failure might not be caused by failing to allocate a specific tenant, in which case the fields + * will be empty. + */ + public static class HostRemovalFailure { + public Optional<Node> host; + public Optional<Node> tenant; + public AllocationFailureReasonList allocationFailures; + + public static HostRemovalFailure none() { + return new HostRemovalFailure( + Optional.empty(), + Optional.empty(), + new AllocationFailureReasonList(List.of())); + } + + public static HostRemovalFailure create(Node host, Node tenant, AllocationFailureReasonList failureReasons) { + return new HostRemovalFailure( + Optional.of(host), + Optional.of(tenant), + failureReasons); + } + + private HostRemovalFailure(Optional<Node> host, Optional<Node> tenant, AllocationFailureReasonList allocationFailures) { + this.host = host; + this.tenant = tenant; + this.allocationFailures = allocationFailures; + } + + @Override + public String toString() { + if (host.isEmpty() || tenant.isEmpty()) return "No removal candidates exists."; + return String.format( + "Failure to remove host %s" + + "\n\tNo new host found for tenant %s:" + + "\n\t\tSingular Reasons: %s" + + "\n\t\tTotal Reasons: %s", + this.host.get().hostname(), + this.tenant.get().hostname(), + this.allocationFailures.singularReasonFailures().toString(), + this.allocationFailures.toString() + ); + } + } + + /** + * Used to describe the resources required for a tenant, and available to a host. + */ + private static class AllocationResources { + NodeResources nodeResources; + int availableIPs; + + public static AllocationResources from(NodeResources nodeResources) { + return new AllocationResources(nodeResources, 1); + } + + public AllocationResources(NodeResources nodeResources, int availableIPs) { + this.nodeResources = nodeResources; + this.availableIPs = availableIPs; + } + + public boolean satisfies(AllocationResources other) { + if (!this.nodeResources.satisfies(other.nodeResources)) return false; + return this.availableIPs >= other.availableIPs; + } + + public AllocationResources subtract(AllocationResources other) { + return new AllocationResources(this.nodeResources.subtract(other.nodeResources), this.availableIPs - other.availableIPs); + } + } + + /** + * Keeps track of the reason why a host rejected an allocation. + */ + private static class AllocationFailureReason { + Node host; + public AllocationFailureReason (Node host) { + this.host = host; + } + public boolean insufficientVcpu = false; + public boolean insufficientMemoryGb = false; + public boolean insufficientDiskGb = false; + public boolean incompatibleDiskSpeed = false; + public boolean insufficientAvailableIPs = false; + public boolean violatesParentHostPolicy = false; + + public int numberOfReasons() { + int n = 0; + if (insufficientVcpu) n++; + if (insufficientMemoryGb) n++; + if (insufficientDiskGb) n++; + if (incompatibleDiskSpeed) n++; + if (insufficientAvailableIPs) n++; + if (violatesParentHostPolicy) n++; + return n; + } + + @Override + public String toString() { + List<String> reasons = new ArrayList<>(); + if (insufficientVcpu) reasons.add("insufficientVcpu"); + if (insufficientMemoryGb) reasons.add("insufficientMemoryGb"); + if (insufficientDiskGb) reasons.add("insufficientDiskGb"); + if (incompatibleDiskSpeed) reasons.add("incompatibleDiskSpeed"); + if (insufficientAvailableIPs) reasons.add("insufficientAvailableIPs"); + if (violatesParentHostPolicy) reasons.add("violatesParentHostPolicy"); + + return String.format("[%s]", String.join(", ", reasons)); + } + } + + /** + * Provides convenient methods for tallying failures. + */ + public static class AllocationFailureReasonList { + private List<AllocationFailureReason> allocationFailureReasons; + public AllocationFailureReasonList(List<AllocationFailureReason> allocationFailureReasons) { + this.allocationFailureReasons = allocationFailureReasons; + } + + public long insufficientVcpu() { return allocationFailureReasons.stream().filter(r -> r.insufficientVcpu).count(); } + public long insufficientMemoryGb() { return allocationFailureReasons.stream().filter(r -> r.insufficientMemoryGb).count(); } + public long insufficientDiskGb() { return allocationFailureReasons.stream().filter(r -> r.insufficientDiskGb).count(); } + public long incompatibleDiskSpeed() { return allocationFailureReasons.stream().filter(r -> r.incompatibleDiskSpeed).count(); } + public long insufficientAvailableIps() { return allocationFailureReasons.stream().filter(r -> r.insufficientAvailableIPs).count(); } + public long violatesParentHostPolicy() { return allocationFailureReasons.stream().filter(r -> r.violatesParentHostPolicy).count(); } + + public AllocationFailureReasonList singularReasonFailures() { + return new AllocationFailureReasonList(allocationFailureReasons.stream() + .filter(reason -> reason.numberOfReasons() == 1).collect(Collectors.toList())); + } + public AllocationFailureReasonList multipleReasonFailures() { + return new AllocationFailureReasonList(allocationFailureReasons.stream() + .filter(reason -> reason.numberOfReasons() > 1).collect(Collectors.toList())); + } + public long size() { + return allocationFailureReasons.size(); + } + @Override + public String toString() { + return String.format("CPU (%3d), Memory (%3d), Disk size (%3d), Disk speed (%3d), IP (%3d), Parent-Host Policy (%3d)", + insufficientVcpu(), insufficientMemoryGb(), insufficientDiskGb(), + incompatibleDiskSpeed(), insufficientAvailableIps(), violatesParentHostPolicy()); + } + } + + public static class AllocationHistory { + public static class Entry { + public Node tenant; + public Node newParent; + public long eligibleParents; + + public Entry(Node tenant, Node newParent, long eligibleParents) { + this.tenant = tenant; + this.newParent = newParent; + this.eligibleParents = eligibleParents; + } + + @Override + public String toString() { + return String.format("%-20s %-65s -> %15s [%3d valid]", + tenant.hostname().replaceFirst("\\..+", ""), + tenant.flavor().resources(), + newParent == null ? "x" : newParent.hostname().replaceFirst("\\..+", ""), + this.eligibleParents + ); + } + } + + public List<Entry> historyEntries; + + public AllocationHistory() { + this.historyEntries = new ArrayList<>(); + } + + public void addEntry(Node tenant, Node newParent, long eligibleParents) { + this.historyEntries.add(new Entry(tenant, newParent, eligibleParents)); + } + + public Set<String> oldParents() { + Set<String> oldParents = new HashSet<>(); + for (var entry : historyEntries) + entry.tenant.parentHostname().ifPresent(oldParents::add); + return oldParents; + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + + String currentParent = ""; + for (var entry : historyEntries) { + String parentName = entry.tenant.parentHostname().orElseThrow(); + if (!parentName.equals(currentParent)) { + currentParent = parentName; + out.append(parentName).append("\n"); + } + out.append(entry.toString()).append("\n"); + } + + return out.toString(); + } + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainer.java index 44d43081ef2..3c47e418b94 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainer.java @@ -1,23 +1,15 @@ package com.yahoo.vespa.hosted.provision.maintenance; -import com.yahoo.config.provision.NodeResources; -import com.yahoo.config.provision.NodeType; import com.yahoo.jdisc.Metric; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.logging.Logger; import java.util.stream.Collectors; -import com.yahoo.vespa.hosted.provision.node.Allocation; - import java.util.*; -import java.util.function.Function; /** * Performs analysis on the node repository to produce metrics that pertain to the capacity of the node repository. @@ -29,7 +21,6 @@ import java.util.function.Function; * @author mgimle */ public class CapacityReportMaintainer extends Maintainer { - private final Metric metric; private final NodeRepository nodeRepository; private static final Logger log = Logger.getLogger(CapacityReportMaintainer.class.getName()); @@ -44,403 +35,20 @@ public class CapacityReportMaintainer extends Maintainer { @Override protected void maintain() { - metric.set("overcommittedHosts", countOvercommittedHosts(), null); - - Optional<HostFailurePath> failurePath = worstCaseHostLossLeadingToFailure(); - if (failurePath.isPresent()) { - int worstCaseHostLoss = failurePath.get().hostsCausingFailure.size(); - metric.set("spareHostCapacity", worstCaseHostLoss - 1, null); - } - } - - protected Optional<HostFailurePath> worstCaseHostLossLeadingToFailure() { - List<Node> hosts = getHosts(); - List<Node> tenants = getTenants(hosts); - Map<String, Node> nodeMap = constructHostnameToNodeMap(hosts); - Map<Node, List<Node>> nodeChildren = constructNodeChildrenMap(tenants, hosts, nodeMap); - Map<Node, AllocationResources> availableResources = constructAvailableResourcesMap(hosts, nodeChildren); - - Map<Node, Integer> timesNodeCanBeRemoved = computeMaximalRepeatedRemovals(hosts, nodeChildren, availableResources); - return greedyHeuristicFindFailurePath(timesNodeCanBeRemoved, hosts, nodeChildren, availableResources); - } - - // We only care about nodes in one of these states. - private Node.State[] relevantNodeStates = { - Node.State.active, - Node.State.inactive, - Node.State.dirty, - Node.State.provisioned, - Node.State.ready, - Node.State.reserved - }; - - private List<Node> getHosts() { - return nodeRepository.getNodes(NodeType.host, relevantNodeStates); - } - - private List<Node> getTenants(List<Node> hosts) { - var parentNames = hosts.stream().map(Node::hostname).collect(Collectors.toSet()); - return nodeRepository.getNodes(NodeType.tenant, relevantNodeStates).stream() - .filter(t -> parentNames.contains(t.parentHostname().orElse(""))) - .collect(Collectors.toList()); - } - - private Optional<HostFailurePath> greedyHeuristicFindFailurePath(Map<Node, Integer> heuristic, List<Node> hosts, - Map<Node, List<Node>> nodeChildren, - Map<Node, AllocationResources> availableResources) { - if (hosts.size() == 0) return Optional.empty(); - List<Node> parentRemovalPriorityList = heuristic.entrySet().stream() - .sorted(Comparator.comparingInt(Map.Entry::getValue)) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - for (int i = 1; i <= parentRemovalPriorityList.size(); i++) { - List<Node> hostsToRemove = parentRemovalPriorityList.subList(0, i); - var hostRemovalFailure = findHostRemovalFailure(hostsToRemove, hosts, nodeChildren, availableResources); - if (hostRemovalFailure.isPresent()) { - HostFailurePath failurePath = new HostFailurePath(); - failurePath.hostsCausingFailure = hostsToRemove; - failurePath.failureReason = hostRemovalFailure.get(); - return Optional.of(failurePath); + if (!nodeRepository.zone().cloud().value().equals("aws")) { + CapacityChecker capacityChecker = new CapacityChecker(this.nodeRepository); + List<Node> overcommittedHosts = capacityChecker.findOvercommittedHosts(); + if (overcommittedHosts.size() != 0) { + log.log(LogLevel.WARNING, String.format("%d nodes are overcommitted! [ %s ]", overcommittedHosts.size(), + overcommittedHosts.stream().map(Node::hostname).collect(Collectors.joining(", ")))); } - } - - throw new IllegalStateException("No path to failure found. This should be impossible!"); - } - - protected int countOvercommittedHosts() { - List<Node> hosts = getHosts(); - List<Node> tenants = getTenants(hosts); - var nodeMap = constructHostnameToNodeMap(hosts); - var nodeChildren = constructNodeChildrenMap(tenants, hosts, nodeMap); - var availableResources = constructAvailableResourcesMap(hosts, nodeChildren); - - List<Node> overcommittedNodes = findOvercommittedNodes(availableResources); - if (overcommittedNodes.size() != 0) { - log.log(LogLevel.WARNING, String.format("%d nodes are overcommitted! [ %s ]", overcommittedNodes.size(), - overcommittedNodes.stream().map(Node::hostname).collect(Collectors.joining(", ")))); - } - return overcommittedNodes.size(); - } - - private Map<String, Node> constructHostnameToNodeMap(List<Node> nodes) { - return nodes.stream().collect(Collectors.toMap(Node::hostname, n -> n)); - } - - private Map<Node, List<Node>> constructNodeChildrenMap(List<Node> tenants, List<Node> hosts, Map<String, Node> hostnameToNode) { - Map<Node, List<Node>> nodeChildren = tenants.stream() - .filter(n -> n.parentHostname().isPresent()) - .filter(n -> hostnameToNode.containsKey(n.parentHostname().get())) - .collect(Collectors.groupingBy( - n -> hostnameToNode.get(n.parentHostname().orElseThrow()))); - - for (var host : hosts) nodeChildren.putIfAbsent(host, List.of()); - - return nodeChildren; - } - - private Map<Node, AllocationResources> constructAvailableResourcesMap(List<Node> hosts, Map<Node, List<Node>> nodeChildren) { - Map<Node, AllocationResources> availableResources = new HashMap<>(); - for (var host : hosts) { - NodeResources hostResources = host.flavor().resources(); - int occupiedIps = 0; - Set<String> ipPool = host.ipAddressPool().asSet(); - for (var child : nodeChildren.get(host)) { - hostResources = hostResources.subtract(child.flavor().resources()); - occupiedIps += child.ipAddresses().stream().filter(ipPool::contains).count(); - } - availableResources.put(host, new AllocationResources(hostResources, host.ipAddressPool().asSet().size() - occupiedIps)); - } - - return availableResources; - } - - /** - * Computes a heuristic for each host, with a lower score indicating a higher perceived likelihood that removing - * the host causes an unrecoverable state - */ - private Map<Node, Integer> computeMaximalRepeatedRemovals(List<Node> hosts, Map<Node, List<Node>> nodeChildren, - Map<Node, AllocationResources> availableResources) { - Map<Node, Integer> timesNodeCanBeRemoved = hosts.stream().collect(Collectors.toMap( - Function.identity(), - _x -> Integer.MAX_VALUE - )); - for (Node host : hosts) { - List<Node> children = nodeChildren.get(host); - if (children.size() == 0) continue; - Map<Node, AllocationResources> resourceMap = new HashMap<>(availableResources); - Map<Node, List<Allocation>> containedAllocations = collateAllocations(nodeChildren); - - int timesHostCanBeRemoved = 0; - Optional<Node> unallocatedTenant; - while (timesHostCanBeRemoved < 1000) { // Arbritrary upper bound - unallocatedTenant = tryAllocateNodes(nodeChildren.get(host), hosts, resourceMap, containedAllocations); - if (unallocatedTenant.isEmpty()) { - timesHostCanBeRemoved++; - } else break; - } - timesNodeCanBeRemoved.put(host, timesHostCanBeRemoved); - } - - return timesNodeCanBeRemoved; - } - - private List<Node> findOvercommittedNodes(Map<Node, AllocationResources> availableResources) { - List<Node> overcommittedNodes = new ArrayList<>(); - for (var entry : availableResources.entrySet()) { - var resources = entry.getValue().nodeResources; - if (resources.vcpu() < 0 || resources.memoryGb() < 0 || resources.diskGb() < 0) { - overcommittedNodes.add(entry.getKey()); - } - } - return overcommittedNodes; - } - - private Map<Node, List<Allocation>> collateAllocations(Map<Node, List<Node>> nodeChildren) { - return nodeChildren.entrySet().stream().collect(Collectors.toMap( - Map.Entry::getKey, - e -> e.getValue().stream() - .map(Node::allocation).flatMap(Optional::stream) - .collect(Collectors.toList()) - )); - } - - /** - * Tests whether it's possible to remove the provided hosts. - * Does not mutate any input variable. - * @return Empty optional if removal is possible, information on what caused the failure otherwise - */ - private Optional<HostRemovalFailure> findHostRemovalFailure(List<Node> hostsToRemove, List<Node> allHosts, - Map<Node, List<Node>> nodechildren, - Map<Node, AllocationResources> availableResources) { - var containedAllocations = collateAllocations(nodechildren); - var resourceMap = new HashMap<>(availableResources); - List<Node> validAllocationTargets = allHosts.stream() - .filter(h -> !hostsToRemove.contains(h)) - .collect(Collectors.toList()); - if (validAllocationTargets.size() == 0) { - return Optional.of(HostRemovalFailure.none()); - } - - for (var host : hostsToRemove) { - Optional<Node> unallocatedNode = tryAllocateNodes(nodechildren.get(host), - validAllocationTargets, resourceMap, containedAllocations); - - if (unallocatedNode.isPresent()) { - AllocationFailureReasonList failures = collateAllocationFailures(unallocatedNode.get(), - validAllocationTargets, resourceMap, containedAllocations); - return Optional.of(HostRemovalFailure.create(host, unallocatedNode.get(), failures)); - } - } - return Optional.empty(); - } + metric.set("overcommittedHosts", overcommittedHosts.size(), null); - /** - * Attempts to allocate the listed nodes to a new host, mutating availableResources and containedAllocations, - * optionally returning the first node to fail, if one does. - * */ - private Optional<Node> tryAllocateNodes(List<Node> nodes, List<Node> hosts, - Map<Node, AllocationResources> availableResources, - Map<Node, List<Allocation>> containedAllocations) { - for (var node : nodes) { - if (!tryAllocateNode(node, hosts, availableResources, containedAllocations)) { - return Optional.of(node); + Optional<CapacityChecker.HostFailurePath> failurePath = capacityChecker.worstCaseHostLossLeadingToFailure(); + if (failurePath.isPresent()) { + int worstCaseHostLoss = failurePath.get().hostsCausingFailure.size(); + metric.set("spareHostCapacity", worstCaseHostLoss - 1, null); } } - return Optional.empty(); - } - - private boolean tryAllocateNode(Node node, List<Node> hosts, - Map<Node, AllocationResources> availableResources, - Map<Node, List<Allocation>> containedAllocations) { - AllocationResources requiredNodeResources = AllocationResources.from(node.flavor().resources()); - for (var host : hosts) { - var availableHostResources = availableResources.get(host); - if (violatesParentHostPolicy(node, host, containedAllocations)) { - continue; - } - if (availableHostResources.satisfies(requiredNodeResources)) { - availableResources.put(host, availableHostResources.subtract(requiredNodeResources)); - if (node.allocation().isPresent()) { - containedAllocations.get(host).add(node.allocation().get()); - } - return true; - } - } - - return false; - } - - private boolean violatesParentHostPolicy(Node node, Node host, Map<Node, List<Allocation>> containedAllocations) { - if (node.allocation().isEmpty()) return false; - Allocation nodeAllocation = node.allocation().get(); - for (var allocation : containedAllocations.get(host)) { - if (allocation.membership().cluster().equalsIgnoringGroupAndVespaVersion(nodeAllocation.membership().cluster()) - && allocation.owner().equals(nodeAllocation.owner())) { - return true; - } - } - return false; - } - - private AllocationFailureReasonList collateAllocationFailures(Node node, List<Node> hosts, - Map<Node, AllocationResources> availableResources, - Map<Node, List<Allocation>> containedAllocations) { - List<AllocationFailureReason> allocationFailureReasons = new ArrayList<>(); - for (var host : hosts) { - AllocationFailureReason reason = new AllocationFailureReason(host); - var availableHostResources = availableResources.get(host); - reason.violatesParentHostPolicy = violatesParentHostPolicy(node, host, containedAllocations); - - NodeResources l = availableHostResources.nodeResources; - NodeResources r = node.flavor().resources(); - if (l.vcpu() < r.vcpu()) { reason.insufficientVcpu = true; } - if (l.memoryGb() < r.memoryGb()) { reason.insufficientMemoryGb = true; } - if (l.diskGb() < r.diskGb()) { reason.insufficientDiskGb = true; } - if (r.diskSpeed() != NodeResources.DiskSpeed.any && r.diskSpeed() != l.diskSpeed()) - { reason.incompatibleDiskSpeed = true; } - if (availableHostResources.availableIPs < 1) { reason.insufficientAvailableIPs = true; } - - allocationFailureReasons.add(reason); - } - - return new AllocationFailureReasonList(allocationFailureReasons); - } - - /** - * Contains the list of hosts that, upon being removed, caused an unrecoverable state, - * as well as the specific host and tenant which caused it. - */ - public static class HostFailurePath { - List<Node> hostsCausingFailure; - HostRemovalFailure failureReason; - } - - /** - * Data class used for detailing why removing the given tenant from the given host was unsuccessful. - * A failure might not be caused by failing to allocate a specific tenant, in which case the fields - * will be empty. - */ - public static class HostRemovalFailure { - Optional<Node> host; - Optional<Node> tenant; - AllocationFailureReasonList failureReasons; - public static HostRemovalFailure none() { - return new HostRemovalFailure( - Optional.empty(), - Optional.empty(), - new AllocationFailureReasonList(List.of())); - } - public static HostRemovalFailure create(Node host, Node tenant, AllocationFailureReasonList failureReasons) { - return new HostRemovalFailure( - Optional.of(host), - Optional.of(tenant), - failureReasons); - } - private HostRemovalFailure(Optional<Node> host, Optional<Node> tenant, AllocationFailureReasonList failureReasons) { - this.host = host; - this.tenant = tenant; - this.failureReasons = failureReasons; - } - } - - /** - * Used to describe the resources required for a tenant, and available to a host. - */ - private static class AllocationResources { - NodeResources nodeResources; - int availableIPs; - - public static AllocationResources from(NodeResources nodeResources) { - return new AllocationResources(nodeResources, 1); - } - - public AllocationResources(NodeResources nodeResources, int availableIPs) { - this.nodeResources = nodeResources; - this.availableIPs = availableIPs; - } - - public boolean satisfies(AllocationResources other) { - if (!this.nodeResources.satisfies(other.nodeResources)) return false; - return this.availableIPs >= other.availableIPs; - } - - public AllocationResources subtract(AllocationResources other) { - return new AllocationResources(this.nodeResources.subtract(other.nodeResources), this.availableIPs - other.availableIPs); - } - } - - /** - * Keeps track of the reason why a host rejected an allocation. - */ - private class AllocationFailureReason { - Node host; - public AllocationFailureReason (Node host) { - this.host = host; - } - public boolean insufficientVcpu = false; - public boolean insufficientMemoryGb = false; - public boolean insufficientDiskGb = false; - public boolean incompatibleDiskSpeed = false; - public boolean insufficientAvailableIPs = false; - public boolean violatesParentHostPolicy = false; - - public int numberOfReasons() { - int n = 0; - if (insufficientVcpu) n++; - if (insufficientMemoryGb) n++; - if (insufficientDiskGb) n++; - if (incompatibleDiskSpeed) n++; - if (insufficientAvailableIPs) n++; - if (violatesParentHostPolicy) n++; - return n; - } - - @Override - public String toString() { - List<String> reasons = new ArrayList<>(); - if (insufficientVcpu) reasons.add("insufficientVcpu"); - if (insufficientMemoryGb) reasons.add("insufficientMemoryGb"); - if (insufficientDiskGb) reasons.add("insufficientDiskGb"); - if (incompatibleDiskSpeed) reasons.add("incompatibleDiskSpeed"); - if (insufficientAvailableIPs) reasons.add("insufficientAvailableIPs"); - if (violatesParentHostPolicy) reasons.add("violatesParentHostPolicy"); - - return String.format("[%s]", String.join(", ", reasons)); - } - } - - /** - * Provides convenient methods for tallying failures. - */ - public static class AllocationFailureReasonList { - private List<AllocationFailureReason> allocationFailureReasons; - public AllocationFailureReasonList(List<AllocationFailureReason> allocationFailureReasons) { - this.allocationFailureReasons = allocationFailureReasons; - } - - long insufficientVcpu() { return allocationFailureReasons.stream().filter(r -> r.insufficientVcpu).count(); } - long insufficientMemoryGb() { return allocationFailureReasons.stream().filter(r -> r.insufficientMemoryGb).count(); } - long insufficientDiskGb() { return allocationFailureReasons.stream().filter(r -> r.insufficientDiskGb).count(); } - long incompatibleDiskSpeed() { return allocationFailureReasons.stream().filter(r -> r.incompatibleDiskSpeed).count(); } - long insufficientAvailableIps() { return allocationFailureReasons.stream().filter(r -> r.insufficientAvailableIPs).count(); } - long violatesParentHostPolicy() { return allocationFailureReasons.stream().filter(r -> r.violatesParentHostPolicy).count(); } - - public AllocationFailureReasonList singularReasonFailures() { - return new AllocationFailureReasonList(allocationFailureReasons.stream() - .filter(reason -> reason.numberOfReasons() == 1).collect(Collectors.toList())); - } - public AllocationFailureReasonList multipleReasonFailures() { - return new AllocationFailureReasonList(allocationFailureReasons.stream() - .filter(reason -> reason.numberOfReasons() > 1).collect(Collectors.toList())); - } - public long size() { - return allocationFailureReasons.size(); - } - @Override - public String toString() { - return String.format("CPU (%3d), Memory (%3d), Disk size (%3d), Disk speed (%3d), IP (%3d), Parent-Host Policy (%3d)", - insufficientVcpu(), insufficientMemoryGb(), insufficientDiskGb(), - incompatibleDiskSpeed(), insufficientAvailableIps(), violatesParentHostPolicy()); - } } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index f661977d933..bb1ff637f08 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -82,7 +82,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { new HostProvisionMaintainer(nodeRepository, durationFromEnv("host_provisioner_interval").orElse(defaults.hostProvisionerInterval), hostProvisioner, flagSource)); hostDeprovisionMaintainer = provisionServiceProvider.getHostProvisioner().map(hostProvisioner -> new HostDeprovisionMaintainer(nodeRepository, durationFromEnv("host_deprovisioner_interval").orElse(defaults.hostDeprovisionerInterval), hostProvisioner, flagSource)); - capacityReportMaintainer = new CapacityReportMaintainer(nodeRepository, metric, durationFromEnv("alert_interval").orElse(defaults.nodeAlerterInterval)); + capacityReportMaintainer = new CapacityReportMaintainer(nodeRepository, metric, durationFromEnv("capacity_report_interval").orElse(defaults.capacityReportInterval)); // The DuperModel is filled with infrastructure applications by the infrastructure provisioner, so explicitly run that now infrastructureProvisioner.maintain(); @@ -143,7 +143,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { private final Duration dirtyExpiry; private final Duration provisionedExpiry; private final Duration rebootInterval; - private final Duration nodeAlerterInterval; + private final Duration capacityReportInterval; private final Duration metricsInterval; private final Duration retiredInterval; private final Duration infrastructureProvisionInterval; @@ -162,7 +162,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { failedExpirerInterval = Duration.ofMinutes(10); provisionedExpiry = Duration.ofHours(4); rebootInterval = Duration.ofDays(30); - nodeAlerterInterval = Duration.ofHours(1); + capacityReportInterval = Duration.ofHours(1); metricsInterval = Duration.ofMinutes(1); infrastructureProvisionInterval = Duration.ofMinutes(1); throttlePolicy = NodeFailer.ThrottlePolicy.hosted; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Agent.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Agent.java index 812c370df5f..f46e2f501bc 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Agent.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Agent.java @@ -7,5 +7,5 @@ package com.yahoo.vespa.hosted.provision.node; * @author bratseth */ public enum Agent { - system, application, operator, NodeRetirer, NodeFailer + system, application, operator, NodeFailer } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java index cf6531c0748..6198183be89 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java @@ -385,7 +385,6 @@ public class NodeSerializer { case "application" : return Agent.application; case "system" : return Agent.system; case "operator" : return Agent.operator; - case "NodeRetirer" : return Agent.system; // TODO: Remove after 7.67 case "NodeFailer" : return Agent.NodeFailer; } throw new IllegalArgumentException("Unknown node event agent '" + eventAgentField.asString() + "'"); @@ -395,7 +394,6 @@ public class NodeSerializer { case application : return "application"; case system : return "system"; case operator : return "operator"; - case NodeRetirer : return "system"; // TODO: Remove after 7.67 case NodeFailer : return "NodeFailer"; } throw new IllegalArgumentException("Serialized form of '" + agent + "' not defined"); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index 94db765c08a..77ca4b01cf2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -5,7 +5,6 @@ import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeResources; -import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; import com.yahoo.config.provision.NodeFlavors; @@ -64,29 +63,20 @@ public class CapacityPolicies { if (requestedResources.isEmpty()) return defaultNodeResources(cluster.type()); - // Flavor is specified and is allocateByLegacyName: Handle legacy flavor specs - if (zone.system() == SystemName.cd) - return flavors.exists(requestedResources.get().legacyName().get()) ? requestedResources.get() - : defaultNodeResources(cluster.type()); - else { - switch (zone.environment()) { - case dev: case test: case staging: return defaultNodeResources(cluster.type()); - default: - flavors.getFlavorOrThrow(requestedResources.get().legacyName().get()); // verify existence - // Return this spec containing the legacy flavor name, not the flavor's capacity object - // which describes the flavors capacity, as the point of legacy allocation is to match - // by name, not by resources - return requestedResources.get(); - } + switch (zone.environment()) { + case dev: case test: case staging: return defaultNodeResources(cluster.type()); + default: + flavors.getFlavorOrThrow(requestedResources.get().legacyName().get()); // verify existence + // Return this spec containing the legacy flavor name, not the flavor's capacity object + // which describes the flavors capacity, as the point of legacy allocation is to match + // by name, not by resources + return requestedResources.get(); } } private NodeResources defaultNodeResources(ClusterSpec.Type clusterType) { if (clusterType == ClusterSpec.Type.admin) - return new NodeResources(0.5, 3, 50); - - if (zone.system().isCd() && zone.environment().isTest()) - new NodeResources(4, 4, 50); + return nodeResourcesForAdminCluster(); return new NodeResources(1.5, 8, 50); } @@ -114,4 +104,9 @@ public class CapacityPolicies { return nodeCount; } + private NodeResources nodeResourcesForAdminCluster() { + double memoryInGb = (zone.system().isCd() ? 2 : 3); + return new NodeResources(0.5, memoryInGb, 50); + } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java index 4f0081b6a7f..ea30fba9798 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java @@ -14,7 +14,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerInstance; -import com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceException; +import com.yahoo.config.provision.exception.LoadBalancerServiceException; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService; import com.yahoo.vespa.hosted.provision.lb.Real; import com.yahoo.vespa.hosted.provision.node.IP; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/HostCapacityResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/HostCapacityResponse.java new file mode 100644 index 00000000000..7b0eb38b628 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/HostCapacityResponse.java @@ -0,0 +1,161 @@ +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.maintenance.CapacityChecker; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class HostCapacityResponse extends HttpResponse { + private final StringBuilder text; + private final Slime slime; + private final CapacityChecker capacityChecker; + private final boolean json; + + public HostCapacityResponse(NodeRepository nodeRepository, HttpRequest request) { + super(200); + capacityChecker = new CapacityChecker(nodeRepository); + + json = request.getBooleanProperty("json"); + String hostsJson = request.getProperty("hosts"); + + text = new StringBuilder(); + slime = new Slime(); + Cursor root = slime.setObject(); + + if (hostsJson != null) { + List<Node> hosts = parseHostList(hostsJson); + hostRemovalResponse(root, hosts); + } else { + zoneFailureReponse(root); + } + } + + private List<Node> parseHostList(String hosts) { + List<String> hostNames = Arrays.asList(hosts.split(",")); + try { + return capacityChecker.nodesFromHostnames(hostNames); + } catch (IllegalArgumentException e) { + throw new NotFoundException(e.getMessage()); + } + } + + private void hostRemovalResponse(Cursor root, List<Node> hosts) { + var failure = capacityChecker.findHostRemovalFailure(hosts); + if (failure.isPresent() && failure.get().failureReason.allocationFailures.size() == 0) { + root.setBool("removalPossible", false); + error(root, "Removing all hosts is trivially impossible."); + } else { + if (json) hostLossPossibleToSlime(root, failure, hosts); + else hostLossPossibleToText(failure, hosts); + } + } + + private void zoneFailureReponse(Cursor root) { + var failurePath = capacityChecker.worstCaseHostLossLeadingToFailure(); + if (failurePath.isPresent()) { + if (json) zoneFailurePathToSlime(root, failurePath.get()); + else zoneFailurePathToText(failurePath.get()); + } else { + error(root, "Node repository contained no hosts."); + } + } + + private void error(Cursor root, String errorMessage) { + if (json) root.setString("error", errorMessage); + else text.append(errorMessage); + } + + private void hostLossPossibleToText(Optional<CapacityChecker.HostFailurePath> failure, List<Node> hostsToRemove) { + text.append(String.format("Attempting to remove %d hosts: ", hostsToRemove.size())); + CapacityChecker.AllocationHistory history = capacityChecker.allocationHistory; + if (failure.isEmpty()) { + text.append("OK\n\n"); + text.append(history); + if (history.oldParents().size() != hostsToRemove.size()) { + long emptyHostCount = hostsToRemove.size() - history.oldParents().size(); + text.append(String.format("\nTrivially removed %d empty host%s.", emptyHostCount, emptyHostCount > 1 ? "s" : "")); + } + } else { + text.append("FAILURE\n\n"); + text.append(history).append("\n"); + text.append(failure.get().failureReason).append("\n\n"); + } + } + + private void zoneFailurePathToText(CapacityChecker.HostFailurePath failurePath) { + text.append(String.format("Found %d hosts. Failure upon trying to remove %d hosts:\n\n", + capacityChecker.getHosts().size(), + failurePath.hostsCausingFailure.size())); + text.append(capacityChecker.allocationHistory).append("\n"); + text.append(failurePath.failureReason); + } + + private void hostLossPossibleToSlime(Cursor root, Optional<CapacityChecker.HostFailurePath> failure, List<Node> hostsToRemove) { + var hosts = root.setArray("hostsToRemove"); + hostsToRemove.forEach(h -> hosts.addString(h.hostname())); + CapacityChecker.AllocationHistory history = capacityChecker.allocationHistory; + root.setBool("removalPossible", failure.isEmpty()); + var arr = root.setArray("history"); + for (var entry : history.historyEntries) { + var object = arr.addObject(); + object.setString("tenant", entry.tenant.hostname()); + if (entry.newParent != null) { + object.setString("newParent", entry.newParent.hostname()); + } + object.setLong("eligibleParents", entry.eligibleParents); + } + } + + private void zoneFailurePathToSlime(Cursor object, CapacityChecker.HostFailurePath failurePath) { + object.setLong("totalHosts", capacityChecker.getHosts().size()); + object.setLong("couldLoseHosts", failurePath.hostsCausingFailure.size()); + failurePath.failureReason.host.ifPresent(host -> + object.setString("failedTenantParent", host.hostname()) + ); + failurePath.failureReason.tenant.ifPresent(tenant -> { + object.setString("failedTenant", tenant.hostname()); + object.setString("failedTenantResources", tenant.flavor().resources().toString()); + tenant.allocation().ifPresent(allocation -> + object.setString("failedTenantAllocation", allocation.toString()) + ); + var explanation = object.setObject("hostCandidateRejectionReasons"); + allocationFailureReasonListToSlime(explanation.setObject("singularReasonFailures"), + failurePath.failureReason.allocationFailures.singularReasonFailures()); + allocationFailureReasonListToSlime(explanation.setObject("totalFailures"), + failurePath.failureReason.allocationFailures); + }); + var details = object.setObject("details"); + hostLossPossibleToSlime(details, Optional.of(failurePath), failurePath.hostsCausingFailure); + } + + private void allocationFailureReasonListToSlime(Cursor root, CapacityChecker.AllocationFailureReasonList allocationFailureReasonList) { + root.setLong("insufficientVcpu", allocationFailureReasonList.insufficientVcpu()); + root.setLong("insufficientMemoryGb", allocationFailureReasonList.insufficientMemoryGb()); + root.setLong("insufficientDiskGb", allocationFailureReasonList.insufficientDiskGb()); + root.setLong("incompatibleDiskSpeed", allocationFailureReasonList.incompatibleDiskSpeed()); + root.setLong("insufficientAvailableIps", allocationFailureReasonList.insufficientAvailableIps()); + root.setLong("violatesParentHostPolicy", allocationFailureReasonList.violatesParentHostPolicy()); + } + + @Override + public void render(OutputStream stream) throws IOException { + if (json) new JsonFormat(true).encode(stream, slime); + else stream.write(text.toString().getBytes()); + } + + @Override + public String getContentType() { + return json ? "application/json" : "text/plain"; + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java index bfbf7775031..9f8f4a804d1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java @@ -77,10 +77,6 @@ public class LoadBalancersResponse extends HttpResponse { realObject.setString("ipAddress", real.ipAddress()); realObject.setLong("port", real.port()); }); - - // TODO(mpolden): The following fields preserves API compatibility. These can be removed once clients stop expecting them - lbObject.setArray("rotations"); - lbObject.setBool("inactive", lb.state() == LoadBalancer.State.inactive); }); new JsonFormat(true).encode(stream, slime); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java index 22318f1ddb4..b2f0998189d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java @@ -102,6 +102,7 @@ public class NodesApiHandler extends LoggingRequestHandler { if (path.equals( "/nodes/v2/command/")) return ResourcesResponse.fromStrings(request.getUri(), "restart", "reboot"); if (path.equals( "/nodes/v2/maintenance/")) return new JobsResponse(nodeRepository.jobControl()); if (path.equals( "/nodes/v2/upgrade/")) return new UpgradeResponse(nodeRepository.infrastructureVersions(), nodeRepository.osVersions(), nodeRepository.dockerImages()); + if (path.startsWith("/nodes/v2/capacity")) return new HostCapacityResponse(nodeRepository, request); throw new NotFoundException("Nothing at path '" + path + "'"); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTest.java index a486f8619c5..1f2112673d1 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTest.java @@ -8,20 +8,19 @@ import org.junit.Test; import java.io.IOException; import java.nio.file.Paths; import java.util.*; + import static org.junit.Assert.fail; import static org.junit.Assert.*; /** * @author mgimle */ -public class CapacityReportMaintainerTest { - private CapacityReportMaintainerTester tester; - private CapacityReportMaintainer capacityReporter; +public class CapacityCheckerTest { + private CapacityCheckerTester tester; @Before public void setup() { - tester = new CapacityReportMaintainerTester(); - capacityReporter = tester.makeCapacityReportMaintainer(); + tester = new CapacityCheckerTester(); } @Test @@ -30,10 +29,9 @@ public class CapacityReportMaintainerTest { tester.cleanRepository(); tester.restoreNodeRepositoryFromJsonFile(Paths.get(path)); - var failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); - if (failurePath.isPresent()) { - assertTrue(tester.nodeRepository.getNodes(NodeType.host).containsAll(failurePath.get().hostsCausingFailure)); - } else fail(); + var failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); + assertTrue(failurePath.isPresent()); + assertTrue(tester.nodeRepository.getNodes(NodeType.host).containsAll(failurePath.get().hostsCausingFailure)); } @Test @@ -41,7 +39,7 @@ public class CapacityReportMaintainerTest { tester.createNodes(7, 4, 10, new NodeResources(-1, 10, 100), 10, 0, new NodeResources(1, 10, 100), 10); - int overcommittedHosts = capacityReporter.countOvercommittedHosts(); + int overcommittedHosts = tester.capacityChecker.findOvercommittedHosts().size(); assertEquals(tester.nodeRepository.getNodes(NodeType.host).size(), overcommittedHosts); } @@ -50,14 +48,14 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 1, 0, new NodeResources(1, 10, 100), 10, 0, new NodeResources(1, 10, 100), 10); - var failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + var failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertFalse("Computing worst case host loss with no hosts should return an empty optional.", failurePath.isPresent()); // Odd edge case that should never be able to occur in prod tester.createNodes(1, 10, 10, new NodeResources(10, 1000, 10000), 100, 1, new NodeResources(10, 1000, 10000), 100); - failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); assertTrue("Computing worst case host loss if all hosts have to be removed should result in an non-empty failureReason with empty nodes.", failurePath.get().failureReason.tenant.isEmpty() && failurePath.get().failureReason.host.isEmpty()); @@ -66,10 +64,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(3, 30, 10, new NodeResources(0, 0, 10000), 1000, 0, new NodeResources(0, 0, 0), 0); - failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("When there are multiple lacking resources, all failures are multipleReasonFailures", failureReasons.size(), failureReasons.multipleReasonFailures().size()); assertEquals(0, failureReasons.singularReasonFailures().size()); @@ -81,10 +79,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 10, 10, new NodeResources(10, 1000, 10000), 1, 10, new NodeResources(10, 1000, 10000), 1); - var failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + var failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("All failures should be due to hosts having a lack of available ip addresses.", failureReasons.singularReasonFailures().insufficientAvailableIps(), failureReasons.size()); } else fail(); @@ -96,10 +94,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 10, 10, new NodeResources(1, 100, 1000), 100, 10, new NodeResources(0, 100, 1000), 100); - var failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + var failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("All failures should be due to hosts lacking cpu cores.", failureReasons.singularReasonFailures().insufficientVcpu(), failureReasons.size()); } else fail(); @@ -107,10 +105,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 10, 10, new NodeResources(10, 1, 1000), 100, 10, new NodeResources(10, 0, 1000), 100); - failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("All failures should be due to hosts lacking memory.", failureReasons.singularReasonFailures().insufficientMemoryGb(), failureReasons.size()); } else fail(); @@ -118,10 +116,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 10, 10, new NodeResources(10, 100, 10), 100, 10, new NodeResources(10, 100, 0), 100); - failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("All failures should be due to hosts lacking disk space.", failureReasons.singularReasonFailures().insufficientDiskGb(), failureReasons.size()); } else fail(); @@ -130,10 +128,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 10, List.of(new NodeResources(1, 10, 100)), 10, new NodeResources(0, 0, 0), 100, 10, new NodeResources(10, 1000, 10000, NodeResources.DiskSpeed.slow), 100); - failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("All empty hosts should be invalid due to having incompatible disk speed.", failureReasons.singularReasonFailures().incompatibleDiskSpeed(), emptyHostsWithSlowDisk); } else fail(); @@ -146,10 +144,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 1, 10, new NodeResources(1, 100, 1000), 100, 10, new NodeResources(10, 1000, 10000), 100); - var failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + var failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertEquals("With only one type of tenant, all failures should be due to violation of the parent host policy.", failureReasons.singularReasonFailures().violatesParentHostPolicy(), failureReasons.size()); } else fail(); @@ -157,10 +155,10 @@ public class CapacityReportMaintainerTest { tester.createNodes(1, 2, 10, new NodeResources(10, 100, 1000), 1, 0, new NodeResources(0, 0, 0), 0); - failurePath = capacityReporter.worstCaseHostLossLeadingToFailure(); + failurePath = tester.capacityChecker.worstCaseHostLossLeadingToFailure(); assertTrue(failurePath.isPresent()); if (failurePath.get().failureReason.tenant.isPresent()) { - var failureReasons = failurePath.get().failureReason.failureReasons; + var failureReasons = failurePath.get().failureReason.allocationFailures; assertNotEquals("Fewer distinct children than hosts should result in some parent host policy violations.", failureReasons.size(), failureReasons.singularReasonFailures().violatesParentHostPolicy()); assertNotEquals(0, failureReasons.singularReasonFailures().violatesParentHostPolicy()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java index ccea4691f10..f5fd0e0526d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityReportMaintainerTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityCheckerTester.java @@ -20,7 +20,6 @@ import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Duration; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -29,22 +28,23 @@ import java.util.stream.IntStream; /** * @author mgimle */ -public class CapacityReportMaintainerTester { +public class CapacityCheckerTester { public static final Zone zone = new Zone(Environment.prod, RegionName.from("us-east")); // Components with state public final ManualClock clock = new ManualClock(); public final NodeRepository nodeRepository; + public CapacityChecker capacityChecker; - CapacityReportMaintainerTester() { + CapacityCheckerTester() { Curator curator = new MockCurator(); NodeFlavors f = new NodeFlavors(new FlavorConfigBuilder().build()); nodeRepository = new NodeRepository(f, curator, clock, zone, new MockNameResolver().mockAnyLookup(), DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"), true); } - CapacityReportMaintainer makeCapacityReportMaintainer() { - return new CapacityReportMaintainer(nodeRepository, new MetricsReporterTest.TestMetric(), Duration.ofDays(1)); + private void updateCapacityChecker() { + this.capacityChecker = new CapacityChecker(this.nodeRepository); } List<NodeModel> createDistinctChildren(int amount, List<NodeResources> childResources) { @@ -167,9 +167,9 @@ public class CapacityReportMaintainerTester { nodes.addAll(createEmptyHosts(numHosts, numEmptyHosts, emptyHostExcessCapacity, emptyHostExcessIps)); nodeRepository.addNodes(nodes); + updateCapacityChecker(); } - NodeResources containingNodeResources(List<NodeResources> resources, NodeResources excessCapacity) { NodeResources usedByChildren = resources.stream() .reduce(new NodeResources(0, 0, 0), NodeResources::add); @@ -278,6 +278,7 @@ public class CapacityReportMaintainerTester { } nodeRepository.addNodes(nodes); + updateCapacityChecker(); } void cleanRepository() { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java index bfb24d30284..8452e8f93bb 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -819,6 +820,28 @@ public class RestApiTest { "{\"message\":\"Cancelled outstanding requests for firmware checks\"}"); } + @Test + public void test_capacity() throws Exception { + assertFile(new Request("http://localhost:8080/nodes/v2/capacity/?json=true"), "capacity-zone.json"); + assertFile(new Request("http://localhost:8080/nodes/v2/capacity?json=true"), "capacity-zone.json"); + + List<String> hostsToRemove = List.of( + "dockerhost1.yahoo.com", + "dockerhost2.yahoo.com", + "dockerhost3.yahoo.com", + "dockerhost4.yahoo.com" + ); + String requestUriTemplate = "http://localhost:8080/nodes/v2/capacity/?json=true&hosts=%s"; + + assertFile(new Request(String.format(requestUriTemplate, + String.join(",", hostsToRemove.subList(0, 3)))), + "capacity-hostremoval-possible.json"); + assertFile(new Request(String.format(requestUriTemplate, + String.join(",", hostsToRemove))), + "capacity-hostremoval-impossible.json"); + } + + /** Tests the rendering of each node separately to make it easier to find errors */ @Test public void test_single_node_rendering() throws Exception { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-hostremoval-impossible.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-hostremoval-impossible.json new file mode 100644 index 00000000000..f3c73e61c91 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-hostremoval-impossible.json @@ -0,0 +1,20 @@ +{ + "hostsToRemove": [ + "dockerhost1.yahoo.com", + "dockerhost2.yahoo.com", + "dockerhost3.yahoo.com", + "dockerhost4.yahoo.com" + ], + "removalPossible": false, + "history": [ + { + "tenant": "host4.yahoo.com", + "newParent": "dockerhost5.yahoo.com", + "eligibleParents": 1 + }, + { + "tenant": "test-node-pool-101-2", + "eligibleParents": 0 + } + ] +}
\ No newline at end of file diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-hostremoval-possible.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-hostremoval-possible.json new file mode 100644 index 00000000000..b896fd9d63a --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-hostremoval-possible.json @@ -0,0 +1,20 @@ +{ + "hostsToRemove": [ + "dockerhost1.yahoo.com", + "dockerhost2.yahoo.com", + "dockerhost3.yahoo.com" + ], + "removalPossible": true, + "history": [ + { + "tenant": "host4.yahoo.com", + "newParent": "dockerhost4.yahoo.com", + "eligibleParents": 2 + }, + { + "tenant": "test-node-pool-101-2", + "newParent": "dockerhost5.yahoo.com", + "eligibleParents": 1 + } + ] +}
\ No newline at end of file diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-zone.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-zone.json new file mode 100644 index 00000000000..9895948e69d --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/capacity-zone.json @@ -0,0 +1,46 @@ +{ + "totalHosts": 5, + "couldLoseHosts": 4, + "failedTenantParent": "dockerhost1.yahoo.com", + "failedTenant": "host4.yahoo.com", + "failedTenantResources": "[vcpu: 1.0, memory: 1.0 Gb, disk 100.0 Gb]", + "failedTenantAllocation": "allocated to tenant3.application3.instance3 as 'content/id3/0/0'", + "hostCandidateRejectionReasons": { + "singularReasonFailures": { + "insufficientVcpu": 0, + "insufficientMemoryGb": 0, + "insufficientDiskGb": 0, + "incompatibleDiskSpeed": 0, + "insufficientAvailableIps": 0, + "violatesParentHostPolicy": 1 + }, + "totalFailures": { + "insufficientVcpu": 0, + "insufficientMemoryGb": 0, + "insufficientDiskGb": 0, + "incompatibleDiskSpeed": 0, + "insufficientAvailableIps": 0, + "violatesParentHostPolicy": 1 + } + }, + "details": { + "hostsToRemove": [ + "dockerhost2.yahoo.com", + "dockerhost1.yahoo.com", + "dockerhost4.yahoo.com", + "dockerhost3.yahoo.com" + ], + "removalPossible": false, + "history": [ + { + "tenant": "test-node-pool-101-2", + "newParent": "dockerhost5.yahoo.com", + "eligibleParents": 1 + }, + { + "tenant": "host4.yahoo.com", + "eligibleParents": 0 + } + ] + } +}
\ No newline at end of file diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers-single.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers-single.json index 67d2c3bfa4b..19e65c2fc25 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers-single.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers-single.json @@ -28,9 +28,7 @@ "ipAddress": "127.0.14.1", "port": 4080 } - ], - "rotations": [], - "inactive": false + ] } ] } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json index c9a45a9c3da..0b05a41af0a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json @@ -28,9 +28,7 @@ "ipAddress": "127.0.10.1", "port": 4080 } - ], - "rotations": [], - "inactive": false + ] }, { "id": "tenant4:application4:instance4:id4", @@ -60,9 +58,7 @@ "ipAddress": "127.0.14.1", "port": 4080 } - ], - "rotations": [], - "inactive": false + ] } ] } diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java index 33e74235862..4b82f278f23 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java @@ -2,11 +2,11 @@ package com.yahoo.vespa.orchestrator.controller; import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.jaxrs.client.JaxRsClientFactory; import com.yahoo.vespa.jaxrs.client.JaxRsStrategy; import com.yahoo.vespa.jaxrs.client.JaxRsStrategyFactory; -import com.yahoo.vespa.jaxrs.client.JerseyJaxRsClientFactory; +import com.yahoo.vespa.jaxrs.client.VespaJerseyJaxRsClientFactory; import java.util.HashSet; import java.util.List; @@ -14,21 +14,21 @@ import java.util.List; /** * @author bakksjo */ -public class RetryingClusterControllerClientFactory implements ClusterControllerClientFactory { +public class RetryingClusterControllerClientFactory extends AbstractComponent implements ClusterControllerClientFactory { // TODO: Figure this port out dynamically. public static final int HARDCODED_CLUSTERCONTROLLER_PORT = 19050; public static final String CLUSTERCONTROLLER_API_PATH = "/"; public static final String CLUSTERCONTROLLER_SCHEME = "http"; - private JaxRsClientFactory jaxRsClientFactory; + private final VespaJerseyJaxRsClientFactory jaxRsClientFactory; @Inject public RetryingClusterControllerClientFactory() { - this(new JerseyJaxRsClientFactory()); + this(new VespaJerseyJaxRsClientFactory("orchestrator-cluster-controller-client")); } - public RetryingClusterControllerClientFactory(JaxRsClientFactory jaxRsClientFactory) { + RetryingClusterControllerClientFactory(VespaJerseyJaxRsClientFactory jaxRsClientFactory) { this.jaxRsClientFactory = jaxRsClientFactory; } @@ -50,4 +50,9 @@ public class RetryingClusterControllerClientFactory implements ClusterController .setMaxIterations(clusterControllers.size() > 1 ? 1 : 2); return new ClusterControllerClientImpl(jaxRsApi, clusterName); } + + @Override + public void deconstruct() { + jaxRsClientFactory.close(); + } } diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactoryTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactoryTest.java index f47b43fa27b..8d5110ac0b6 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactoryTest.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactoryTest.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.orchestrator.controller; import com.yahoo.test.ManualClock; import com.yahoo.vespa.applicationmodel.HostName; -import com.yahoo.vespa.jaxrs.client.JaxRsClientFactory; +import com.yahoo.vespa.jaxrs.client.VespaJerseyJaxRsClientFactory; import com.yahoo.vespa.orchestrator.OrchestratorContext; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -27,7 +27,7 @@ public class RetryingClusterControllerClientFactoryTest { @Test public void verifyJerseyCallForSetNodeState() throws IOException { - JaxRsClientFactory clientFactory = mock(JaxRsClientFactory.class); + VespaJerseyJaxRsClientFactory clientFactory = mock(VespaJerseyJaxRsClientFactory.class); ClusterControllerJaxRsApi api = mock(ClusterControllerJaxRsApi.class); when(clientFactory.createClient(any())).thenReturn(api); RetryingClusterControllerClientFactory factory = new RetryingClusterControllerClientFactory(clientFactory); diff --git a/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp b/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp index f77d7705a66..3ae13d6b21c 100644 --- a/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp +++ b/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp @@ -157,7 +157,7 @@ doIterate(PersistenceProvider& spi, Context context(defaultLoadType, Priority(0), Trace::TraceLevel(0)); IterateResult result(spi.iterate(id, maxByteSize, context)); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); chunks.push_back(Chunk{result.steal_entries()}); if (result.isCompleted() @@ -214,13 +214,13 @@ iterateBucket(PersistenceProvider& spi, versions, context); - EXPECT_EQ(Result::NONE, iter.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, iter.getErrorCode()); while (true) { IterateResult result = spi.iterate(iter.getIteratorId(), std::numeric_limits<int64_t>().max(), context); - if (result.getErrorCode() != Result::NONE) { + if (result.getErrorCode() != Result::ErrorType::NONE) { return std::vector<DocEntry::UP>(); } auto list = result.steal_entries(); @@ -639,7 +639,7 @@ TEST_F(ConformanceTest, testPutNewDocumentVersion) GetResult gr = spi->get(bucket, document::AllFields(), doc1->getId(), context); - EXPECT_EQ(Result::NONE, gr.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, gr.getErrorCode()); EXPECT_EQ(Timestamp(4), gr.getTimestamp()); if (!((*doc2)==gr.getDocument())) { @@ -691,7 +691,7 @@ TEST_F(ConformanceTest, testPutOlderDocumentVersion) GetResult gr = spi->get(bucket, document::AllFields(), doc1->getId(), context); - EXPECT_EQ(Result::NONE, gr.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, gr.getErrorCode()); EXPECT_EQ(Timestamp(5), gr.getTimestamp()); EXPECT_EQ(*doc1, gr.getDocument()); } @@ -822,7 +822,7 @@ TEST_F(ConformanceTest, testRemove) doc1->getId(), context); - EXPECT_EQ(Result::NONE, getResult.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, getResult.getErrorCode()); EXPECT_EQ(Timestamp(0), getResult.getTimestamp()); EXPECT_TRUE(!getResult.hasDocument()); } @@ -848,7 +848,7 @@ TEST_F(ConformanceTest, testRemoveMerge) removeId, context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, removeResult.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, removeResult.getErrorCode()); EXPECT_EQ(false, removeResult.wasFound()); } { @@ -876,7 +876,7 @@ TEST_F(ConformanceTest, testRemoveMerge) removeId, context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, removeResult.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, removeResult.getErrorCode()); EXPECT_EQ(false, removeResult.wasFound()); } // Old entry may or may not be present, depending on the provider. @@ -904,7 +904,7 @@ TEST_F(ConformanceTest, testRemoveMerge) removeId, context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, removeResult.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, removeResult.getErrorCode()); EXPECT_EQ(false, removeResult.wasFound()); } { @@ -957,7 +957,7 @@ TEST_F(ConformanceTest, testUpdate) context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(3), result.getExistingTimestamp()); } @@ -967,7 +967,7 @@ TEST_F(ConformanceTest, testUpdate) doc1->getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(4), result.getTimestamp()); EXPECT_EQ(document::IntFieldValue(42), static_cast<document::IntFieldValue&>( @@ -983,7 +983,7 @@ TEST_F(ConformanceTest, testUpdate) doc1->getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(0), result.getTimestamp()); EXPECT_TRUE(!result.hasDocument()); } @@ -993,13 +993,13 @@ TEST_F(ConformanceTest, testUpdate) context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(0), result.getExistingTimestamp()); } { GetResult result = spi->get(bucket, document::AllFields(), doc1->getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(0), result.getTimestamp()); EXPECT_TRUE(!result.hasDocument()); } @@ -1010,13 +1010,13 @@ TEST_F(ConformanceTest, testUpdate) // but since CreateIfNonExistent is set it should be auto-created anyway. UpdateResult result = spi->update(bucket, Timestamp(7), update, context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(7), result.getExistingTimestamp()); } { GetResult result = spi->get(bucket, document::AllFields(), doc1->getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(7), result.getTimestamp()); EXPECT_EQ(document::IntFieldValue(42), reinterpret_cast<document::IntFieldValue&>( @@ -1039,7 +1039,7 @@ TEST_F(ConformanceTest, testGet) GetResult result = spi->get(bucket, document::AllFields(), doc1->getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(0), result.getTimestamp()); } @@ -1060,7 +1060,7 @@ TEST_F(ConformanceTest, testGet) GetResult result = spi->get(bucket, document::AllFields(), doc1->getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(0), result.getTimestamp()); } } @@ -1077,7 +1077,7 @@ TEST_F(ConformanceTest, testIterateCreateIterator) spi::CreateIteratorResult result( createIterator(*spi, b, createSelection(""))); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); // Iterator ID 0 means invalid iterator, so cannot be returned // from a successful createIterator call. EXPECT_TRUE(result.getIteratorId() != IteratorId(0)); @@ -1096,7 +1096,7 @@ TEST_F(ConformanceTest, testIterateWithUnknownId) IteratorId unknownId(123); IterateResult result(spi->iterate(unknownId, 1024, context)); - EXPECT_EQ(Result::PERMANENT_ERROR, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::PERMANENT_ERROR, result.getErrorCode()); } TEST_F(ConformanceTest, testIterateDestroyIterator) @@ -1111,7 +1111,7 @@ TEST_F(ConformanceTest, testIterateDestroyIterator) CreateIteratorResult iter(createIterator(*spi, b, createSelection(""))); { IterateResult result(spi->iterate(iter.getIteratorId(), 1024, context)); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); } { @@ -1122,7 +1122,7 @@ TEST_F(ConformanceTest, testIterateDestroyIterator) // Iteration should now fail { IterateResult result(spi->iterate(iter.getIteratorId(), 1024, context)); - EXPECT_EQ(Result::PERMANENT_ERROR, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::PERMANENT_ERROR, result.getErrorCode()); } { Result destroyResult( @@ -1422,7 +1422,7 @@ TEST_F(ConformanceTest, testIterationRequiringDocumentIdOnlyMatching) CreateIteratorResult iter( createIterator(*spi, b, sel, NEWEST_DOCUMENT_OR_REMOVE)); - EXPECT_TRUE(iter.getErrorCode() == Result::NONE); + EXPECT_TRUE(iter.getErrorCode() == Result::ErrorType::NONE); std::vector<Chunk> chunks = doIterate(*spi, iter.getIteratorId(), 4096); std::vector<DocAndTimestamp> docs; @@ -1444,14 +1444,14 @@ TEST_F(ConformanceTest, testIterateBadDocumentSelection) { CreateIteratorResult iter( createIterator(*spi, b, createSelection("the muppet show"))); - if (iter.getErrorCode() == Result::NONE) { + if (iter.getErrorCode() == Result::ErrorType::NONE) { IterateResult result( spi->iterate(iter.getIteratorId(), 4096, context)); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(size_t(0), result.getEntries().size()); EXPECT_EQ(true, result.isCompleted()); } else { - EXPECT_EQ(Result::PERMANENT_ERROR, iter.getErrorCode()); + EXPECT_EQ(Result::ErrorType::PERMANENT_ERROR, iter.getErrorCode()); EXPECT_EQ(IteratorId(0), iter.getIteratorId()); } } @@ -1461,14 +1461,14 @@ TEST_F(ConformanceTest, testIterateBadDocumentSelection) b, createSelection( "unknownddoctype.something=thatthing"))); - if (iter.getErrorCode() == Result::NONE) { + if (iter.getErrorCode() == Result::ErrorType::NONE) { IterateResult result(spi->iterate( iter.getIteratorId(), 4096, context)); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(size_t(0), result.getEntries().size()); EXPECT_EQ(true, result.isCompleted()); } else { - EXPECT_EQ(Result::PERMANENT_ERROR, iter.getErrorCode()); + EXPECT_EQ(Result::ErrorType::PERMANENT_ERROR, iter.getErrorCode()); EXPECT_EQ(IteratorId(0), iter.getIteratorId()); } } @@ -1491,7 +1491,7 @@ TEST_F(ConformanceTest, testIterateAlreadyCompleted) verifyDocs(docs, chunks); IterateResult result(spi->iterate(iter.getIteratorId(), 4096, context)); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(size_t(0), result.getEntries().size()); EXPECT_TRUE(result.isCompleted()); @@ -1511,7 +1511,7 @@ TEST_F(ConformanceTest, testIterateEmptyBucket) CreateIteratorResult iter(createIterator(*spi, b, sel)); IterateResult result(spi->iterate(iter.getIteratorId(), 4096, context)); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(size_t(0), result.getEntries().size()); EXPECT_TRUE(result.isCompleted()); @@ -1556,7 +1556,7 @@ testDeleteBucketPostCondition(const PersistenceProvider::UP &spi, doc1.getId(), context); - EXPECT_EQ(Result::NONE, result.getErrorCode()); + EXPECT_EQ(Result::ErrorType::NONE, result.getErrorCode()); EXPECT_EQ(Timestamp(0), result.getTimestamp()); } } @@ -2152,7 +2152,7 @@ TEST_F(ConformanceTest, testMaintain) spi->put(bucket, Timestamp(3), doc1, context); spi->flush(bucket, context); - EXPECT_EQ(Result::NONE, + EXPECT_EQ(Result::ErrorType::NONE, spi->maintain(bucket, LOW).getErrorCode()); } diff --git a/persistence/src/vespa/persistence/dummyimpl/dummypersistence.cpp b/persistence/src/vespa/persistence/dummyimpl/dummypersistence.cpp index 65759a0c783..6834f453695 100644 --- a/persistence/src/vespa/persistence/dummyimpl/dummypersistence.cpp +++ b/persistence/src/vespa/persistence/dummyimpl/dummypersistence.cpp @@ -409,7 +409,7 @@ DummyPersistence::setActiveState(const Bucket& b, BucketContentGuard::UP bc(acquireBucketWithLock(b)); if (!bc.get()) { - return BucketInfoResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return BucketInfoResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } (*bc)->setActive(newState == BucketInfo::ACTIVE); return Result(); @@ -424,7 +424,7 @@ DummyPersistence::getBucketInfo(const Bucket& b) const if (!bc.get()) { LOG(debug, "getBucketInfo(%s) : (bucket not found)", b.toString().c_str()); - return BucketInfoResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return BucketInfoResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } BucketInfo info((*bc)->getBucketInfo()); @@ -446,7 +446,7 @@ DummyPersistence::put(const Bucket& b, Timestamp t, const Document::SP& doc, assert(b.getBucketSpace() == FixedBucketSpaces::default_space()); BucketContentGuard::UP bc(acquireBucketWithLock(b)); if (!bc.get()) { - return BucketInfoResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return BucketInfoResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } DocEntry::SP existing = (*bc)->getEntry(t); @@ -454,7 +454,7 @@ DummyPersistence::put(const Bucket& b, Timestamp t, const Document::SP& doc, if (doc->getId() == *existing->getDocumentId()) { return Result(); } else { - return Result(Result::TIMESTAMP_EXISTS, + return Result(Result::ErrorType::TIMESTAMP_EXISTS, "Timestamp already existed"); } } @@ -474,7 +474,7 @@ DummyPersistence::maintain(const Bucket& b, if (_simulateMaintainFailure) { BucketContentGuard::UP bc(acquireBucketWithLock(b)); if (!bc.get()) { - return BucketInfoResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return BucketInfoResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } if (!(*bc)->_entries.empty()) { @@ -503,7 +503,7 @@ DummyPersistence::remove(const Bucket& b, BucketContentGuard::UP bc(acquireBucketWithLock(b)); if (!bc.get()) { - return RemoveResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return RemoveResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } DocEntry::SP entry((*bc)->getEntry(did)); @@ -564,13 +564,13 @@ DummyPersistence::createIterator( true).release()); if (!docSelection.get()) { return CreateIteratorResult( - Result::PERMANENT_ERROR, + Result::ErrorType::PERMANENT_ERROR, "Got invalid/unparseable document selection string"); } } BucketContentGuard::UP bc(acquireBucketWithLock(b, LockMode::Shared)); if (!bc.get()) { - return CreateIteratorResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return CreateIteratorResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } Iterator* it; @@ -650,7 +650,7 @@ DummyPersistence::iterate(IteratorId id, uint64_t maxByteSize, Context& ctx) con vespalib::MonitorGuard lock(_monitor); std::map<IteratorId, Iterator::UP>::iterator iter(_iterators.find(id)); if (iter == _iterators.end()) { - return IterateResult(Result::PERMANENT_ERROR, + return IterateResult(Result::ErrorType::PERMANENT_ERROR, "Bug! Used iterate without sending createIterator first"); } it = iter->second.get(); @@ -659,7 +659,7 @@ DummyPersistence::iterate(IteratorId id, uint64_t maxByteSize, Context& ctx) con BucketContentGuard::UP bc(acquireBucketWithLock(it->_bucket, LockMode::Shared)); if (!bc.get()) { ctx.trace(9, "finished iterate(); bucket not found"); - return IterateResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return IterateResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } LOG(debug, "Iterator %" PRIu64 " acquired bucket lock", uint64_t(id)); @@ -776,7 +776,7 @@ DummyPersistence::split(const Bucket& source, BucketContentGuard::UP sourceGuard(acquireBucketWithLock(source)); if (!sourceGuard.get()) { LOG(debug, "%s not found", source.toString().c_str()); - return Result(Result::TRANSIENT_ERROR, "Bucket not found"); + return Result(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } BucketContentGuard::UP target1Guard(acquireBucketWithLock(target1)); BucketContentGuard::UP target2Guard(acquireBucketWithLock(target2)); @@ -863,7 +863,7 @@ DummyPersistence::revert(const Bucket& b, Timestamp t, Context&) BucketContentGuard::UP bc(acquireBucketWithLock(b)); if (!bc.get()) { - return BucketInfoResult(Result::TRANSIENT_ERROR, "Bucket not found"); + return BucketInfoResult(Result::ErrorType::TRANSIENT_ERROR, "Bucket not found"); } BucketContent& content(**bc); diff --git a/persistence/src/vespa/persistence/spi/result.cpp b/persistence/src/vespa/persistence/spi/result.cpp index 4aa01d22649..024f5595102 100644 --- a/persistence/src/vespa/persistence/spi/result.cpp +++ b/persistence/src/vespa/persistence/spi/result.cpp @@ -3,6 +3,7 @@ #include "result.h" #include <vespa/document/fieldvalue/document.h> #include <vespa/vespalib/stllike/asciistream.h> +#include <ostream> namespace storage::spi { @@ -13,7 +14,7 @@ Result::~Result() { } vespalib::string Result::toString() const { vespalib::asciistream os; - os << "Result(" << _errorCode << ", " << _errorMessage << ")"; + os << "Result(" << static_cast<int>(_errorCode) << ", " << _errorMessage << ")"; return os.str(); } @@ -22,6 +23,10 @@ operator << (std::ostream & os, const Result & r) { return os << r.toString(); } +std::ostream & operator << (std::ostream & os, const Result::ErrorType &errorCode) { + return os << static_cast<int>(errorCode); +} + GetResult::GetResult(Document::UP doc, Timestamp timestamp) : Result(), _timestamp(timestamp), diff --git a/persistence/src/vespa/persistence/spi/result.h b/persistence/src/vespa/persistence/spi/result.h index 0daa784caa4..fe8e74706bb 100644 --- a/persistence/src/vespa/persistence/spi/result.h +++ b/persistence/src/vespa/persistence/spi/result.h @@ -13,7 +13,7 @@ class Result { public: typedef std::unique_ptr<Result> UP; - enum ErrorType { + enum class ErrorType { NONE, TRANSIENT_ERROR, PERMANENT_ERROR, @@ -26,7 +26,7 @@ public: /** * Constructor to use for a result where there is no error. */ - Result() : _errorCode(NONE), _errorMessage() {} + Result() : _errorCode(ErrorType::NONE), _errorMessage() {} /** * Constructor to use when an error has been detected. @@ -46,7 +46,7 @@ public: } bool hasError() const { - return _errorCode != NONE; + return _errorCode != ErrorType::NONE; } ErrorType getErrorCode() const { @@ -66,6 +66,8 @@ private: std::ostream & operator << (std::ostream & os, const Result & r); +std::ostream & operator << (std::ostream & os, const Result::ErrorType &errorCode); + class BucketInfoResult : public Result { public: /** diff --git a/searchcommon/src/vespa/searchcommon/common/schemaconfigurer.cpp b/searchcommon/src/vespa/searchcommon/common/schemaconfigurer.cpp index a46f99d158d..3750f8d1d62 100644 --- a/searchcommon/src/vespa/searchcommon/common/schemaconfigurer.cpp +++ b/searchcommon/src/vespa/searchcommon/common/schemaconfigurer.cpp @@ -28,9 +28,9 @@ Schema::DataType convertIndexDataType(const IndexschemaConfig::Indexfield::Datatype &type) { switch (type) { - case IndexschemaConfig::Indexfield::STRING: + case IndexschemaConfig::Indexfield::Datatype::STRING: return DataType::STRING; - case IndexschemaConfig::Indexfield::INT64: + case IndexschemaConfig::Indexfield::Datatype::INT64: return DataType::INT64; } return DataType::STRING; @@ -41,11 +41,11 @@ Schema::CollectionType convertIndexCollectionType(const IndexschemaConfig::Indexfield::Collectiontype &type) { switch (type) { - case IndexschemaConfig::Indexfield::SINGLE: + case IndexschemaConfig::Indexfield::Collectiontype::SINGLE: return CollectionType::SINGLE; - case IndexschemaConfig::Indexfield::ARRAY: + case IndexschemaConfig::Indexfield::Collectiontype::ARRAY: return CollectionType::ARRAY; - case IndexschemaConfig::Indexfield::WEIGHTEDSET: + case IndexschemaConfig::Indexfield::Collectiontype::WEIGHTEDSET: return CollectionType::WEIGHTEDSET; } return CollectionType::SINGLE; diff --git a/searchcore/src/apps/proton/downpersistence.cpp b/searchcore/src/apps/proton/downpersistence.cpp index 33ad4bc5024..4b911eb6d2b 100644 --- a/searchcore/src/apps/proton/downpersistence.cpp +++ b/searchcore/src/apps/proton/downpersistence.cpp @@ -10,7 +10,7 @@ namespace storage::spi { namespace { -Result errorResult(Result::FATAL_ERROR, "Node is down"); +Result errorResult(Result::ErrorType::FATAL_ERROR, "Node is down"); } diff --git a/searchcore/src/tests/proton/documentdb/documentdbconfigscout/documentdbconfigscout_test.cpp b/searchcore/src/tests/proton/documentdb/documentdbconfigscout/documentdbconfigscout_test.cpp index ce07858ea0b..f76743b9480 100644 --- a/searchcore/src/tests/proton/documentdb/documentdbconfigscout/documentdbconfigscout_test.cpp +++ b/searchcore/src/tests/proton/documentdb/documentdbconfigscout/documentdbconfigscout_test.cpp @@ -216,9 +216,9 @@ setupLiveAttributes(AttributesConfigBuilder::AttributeVector &attributes) attributes.push_back(setupFastSearchAttribute("a0")); attributes.push_back(setupFastSearchAndMoreAttribute("a1")); attributes.push_back(setupFastSearchAttribute("a2")); - attributes.back().datatype = AttributesConfig::Attribute::INT8; + attributes.back().datatype = AttributesConfig::Attribute::Datatype::INT8; attributes.push_back(setupFastSearchAttribute("a3")); - attributes.back().collectiontype = AttributesConfig::Attribute::ARRAY; + attributes.back().collectiontype = AttributesConfig::Attribute::Collectiontype::ARRAY; attributes.push_back(setupFastSearchAttribute("a4")); attributes.back().createifnonexistent = true; } diff --git a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp index 39fc93d5725..4b3b68a85ea 100644 --- a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp +++ b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp @@ -684,7 +684,7 @@ TEST_F("require that put is rejected if resource limit is reached", FeedHandlerF FeedTokenContext token; f.handler.performOperation(std::move(token.token), std::move(op)); EXPECT_EQUAL(0, f.feedView.put_count); - EXPECT_EQUAL(Result::RESOURCE_EXHAUSTED, token.getResult()->getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::RESOURCE_EXHAUSTED, token.getResult()->getErrorCode()); EXPECT_EQUAL("Put operation rejected for document 'id:test:searchdocument::foo' of type 'searchdocument': 'Attribute resource limit reached'", token.getResult()->getErrorMessage()); } @@ -700,7 +700,7 @@ TEST_F("require that update is rejected if resource limit is reached", FeedHandl f.handler.performOperation(std::move(token.token), std::move(op)); EXPECT_EQUAL(0, f.feedView.update_count); EXPECT_TRUE(dynamic_cast<const UpdateResult *>(token.getResult())); - EXPECT_EQUAL(Result::RESOURCE_EXHAUSTED, token.getResult()->getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::RESOURCE_EXHAUSTED, token.getResult()->getErrorCode()); EXPECT_EQUAL("Update operation rejected for document 'id:test:searchdocument::foo' of type 'searchdocument': 'Attribute resource limit reached'", token.getResult()->getErrorMessage()); } @@ -715,7 +715,7 @@ TEST_F("require that remove is NOT rejected if resource limit is reached", FeedH FeedTokenContext token; f.handler.performOperation(std::move(token.token), std::move(op)); EXPECT_EQUAL(1, f.feedView.remove_count); - EXPECT_EQUAL(Result::NONE, token.getResult()->getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::NONE, token.getResult()->getErrorCode()); EXPECT_EQUAL("", token.getResult()->getErrorMessage()); } @@ -738,7 +738,7 @@ checkUpdate(FeedHandlerFixture &f, SchemaContext &schemaContext, EXPECT_TRUE(dynamic_cast<const UpdateResult *>(token.getResult())); if (expectReject) { TEST_DO(f.feedView.checkCounts(0, 0u, 0, 0u)); - EXPECT_EQUAL(Result::TRANSIENT_ERROR, token.getResult()->getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::TRANSIENT_ERROR, token.getResult()->getErrorCode()); if (fieldName == "tensor2") { EXPECT_EQUAL("Update operation rejected for document 'id:test:searchdocument::foo' of type 'searchdocument': 'Wrong tensor type: Field tensor type is 'tensor(x{},y{})' but other tensor type is 'tensor(x{})''", token.getResult()->getErrorMessage()); @@ -752,7 +752,7 @@ checkUpdate(FeedHandlerFixture &f, SchemaContext &schemaContext, } else { TEST_DO(f.feedView.checkCounts(0, 0u, 1, 16u)); } - EXPECT_EQUAL(Result::NONE, token.getResult()->getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::NONE, token.getResult()->getErrorCode()); EXPECT_EQUAL("", token.getResult()->getErrorMessage()); } } diff --git a/searchcore/src/tests/proton/persistenceengine/persistenceengine_test.cpp b/searchcore/src/tests/proton/persistenceengine/persistenceengine_test.cpp index 5484b8eacf0..cdc0e8656d8 100644 --- a/searchcore/src/tests/proton/persistenceengine/persistenceengine_test.cpp +++ b/searchcore/src/tests/proton/persistenceengine/persistenceengine_test.cpp @@ -469,7 +469,7 @@ TEST_F("require that puts are routed to handler", SimpleFixture) assertHandler(bucket1, tstamp1, docId1, f.hset.handler1); assertHandler(bucket1, tstamp1, docId2, f.hset.handler2); - EXPECT_EQUAL(Result(Result::PERMANENT_ERROR, "No handler for document type 'type3'"), + EXPECT_EQUAL(Result(Result::ErrorType::PERMANENT_ERROR, "No handler for document type 'type3'"), f.engine.put(bucket1, tstamp1, doc3, context)); } @@ -477,7 +477,7 @@ TEST_F("require that puts are routed to handler", SimpleFixture) TEST_F("require that puts with old id scheme are rejected", SimpleFixture) { storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); - EXPECT_EQUAL(Result(Result::PERMANENT_ERROR, "Old id scheme not supported in elastic mode (doc:old:id-scheme)"), + EXPECT_EQUAL(Result(Result::ErrorType::PERMANENT_ERROR, "Old id scheme not supported in elastic mode (doc:old:id-scheme)"), f.engine.put(bucket1, tstamp1, old_doc, context)); } @@ -490,7 +490,7 @@ TEST_F("require that put is rejected if resource limit is reached", SimpleFixtur storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); EXPECT_EQUAL( - Result(Result::RESOURCE_EXHAUSTED, + Result(Result::ErrorType::RESOURCE_EXHAUSTED, "Put operation rejected for document 'doc:old:id-scheme': 'Disk is full'"), f.engine.put(bucket1, tstamp1, old_doc, context)); } @@ -512,7 +512,7 @@ TEST_F("require that updates are routed to handler", SimpleFixture) assertHandler(bucket1, tstamp1, docId2, f.hset.handler2); EXPECT_EQUAL(tstamp3, ur.getExistingTimestamp()); - EXPECT_EQUAL(Result(Result::PERMANENT_ERROR, "No handler for document type 'type3'"), + EXPECT_EQUAL(Result(Result::ErrorType::PERMANENT_ERROR, "No handler for document type 'type3'"), f.engine.update(bucket1, tstamp1, upd3, context)); } @@ -522,7 +522,7 @@ TEST_F("require that updates with old id scheme are rejected", SimpleFixture) storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); - EXPECT_EQUAL(UpdateResult(Result::PERMANENT_ERROR, "Old id scheme not supported in elastic mode (doc:old:id-scheme)"), + EXPECT_EQUAL(UpdateResult(Result::ErrorType::PERMANENT_ERROR, "Old id scheme not supported in elastic mode (doc:old:id-scheme)"), f.engine.update(bucket1, tstamp1, old_upd, context)); } @@ -531,7 +531,7 @@ TEST_F("require that updates with bad ids are rejected", SimpleFixture) storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); - EXPECT_EQUAL(UpdateResult(Result::PERMANENT_ERROR, "Update operation rejected due to bad id (id:type2:type2::1, type1)"), + EXPECT_EQUAL(UpdateResult(Result::ErrorType::PERMANENT_ERROR, "Update operation rejected due to bad id (id:type2:type2::1, type1)"), f.engine.update(bucket1, tstamp1, bad_id_upd, context)); } @@ -544,7 +544,7 @@ TEST_F("require that update is rejected if resource limit is reached", SimpleFix Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); EXPECT_EQUAL( - Result(Result::RESOURCE_EXHAUSTED, + Result(Result::ErrorType::RESOURCE_EXHAUSTED, "Update operation rejected for document 'id:type1:type1::1': 'Disk is full'"), f.engine.update(bucket1, tstamp1, upd1, context)); } @@ -559,7 +559,7 @@ TEST_F("require that removes are routed to handlers", SimpleFixture) assertHandler(bucket0, tstamp0, docId0, f.hset.handler2); EXPECT_FALSE(rr.wasFound()); EXPECT_TRUE(rr.hasError()); - EXPECT_EQUAL(Result(Result::PERMANENT_ERROR, "No handler for document type 'type3'"), rr); + EXPECT_EQUAL(Result(Result::ErrorType::PERMANENT_ERROR, "No handler for document type 'type3'"), rr); f.hset.handler1.setExistingTimestamp(tstamp2); rr = f.engine.remove(bucket1, tstamp1, docId1, context); @@ -590,7 +590,7 @@ TEST_F("require that removes with old id scheme are rejected", SimpleFixture) storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); - EXPECT_EQUAL(RemoveResult(Result::PERMANENT_ERROR, "Old id scheme not supported in elastic mode (doc:old:id-scheme)"), + EXPECT_EQUAL(RemoveResult(Result::ErrorType::PERMANENT_ERROR, "Old id scheme not supported in elastic mode (doc:old:id-scheme)"), f.engine.remove(bucket1, tstamp1, old_docId, context)); } @@ -626,11 +626,11 @@ TEST_F("require that setClusterState() is routed to handlers", SimpleFixture) TEST_F("require that setActiveState() is routed to handlers and merged", SimpleFixture) { - f.hset.handler1.bucketStateResult = Result(Result::TRANSIENT_ERROR, "err1"); - f.hset.handler2.bucketStateResult = Result(Result::PERMANENT_ERROR, "err2"); + f.hset.handler1.bucketStateResult = Result(Result::ErrorType::TRANSIENT_ERROR, "err1"); + f.hset.handler2.bucketStateResult = Result(Result::ErrorType::PERMANENT_ERROR, "err2"); Result result = f.engine.setActiveState(bucket1, storage::spi::BucketInfo::NOT_ACTIVE); - EXPECT_EQUAL(Result::PERMANENT_ERROR, result.getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::PERMANENT_ERROR, result.getErrorCode()); EXPECT_EQUAL("err1, err2", result.getErrorMessage()); EXPECT_EQUAL(storage::spi::BucketInfo::NOT_ACTIVE, f.hset.handler1.lastBucketState); EXPECT_EQUAL(storage::spi::BucketInfo::NOT_ACTIVE, f.hset.handler2.lastBucketState); @@ -655,11 +655,11 @@ TEST_F("require that createBucket() is routed to handlers and merged", SimpleFix { storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); - f.hset.handler1._createBucketResult = Result(Result::TRANSIENT_ERROR, "err1a"); - f.hset.handler2._createBucketResult = Result(Result::PERMANENT_ERROR, "err2a"); + f.hset.handler1._createBucketResult = Result(Result::ErrorType::TRANSIENT_ERROR, "err1a"); + f.hset.handler2._createBucketResult = Result(Result::ErrorType::PERMANENT_ERROR, "err2a"); Result result = f.engine.createBucket(bucket1, context); - EXPECT_EQUAL(Result::PERMANENT_ERROR, result.getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::PERMANENT_ERROR, result.getErrorCode()); EXPECT_EQUAL("err1a, err2a", result.getErrorMessage()); } @@ -668,11 +668,11 @@ TEST_F("require that deleteBucket() is routed to handlers and merged", SimpleFix { storage::spi::LoadType loadType(0, "default"); Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); - f.hset.handler1.deleteBucketResult = Result(Result::TRANSIENT_ERROR, "err1"); - f.hset.handler2.deleteBucketResult = Result(Result::PERMANENT_ERROR, "err2"); + f.hset.handler1.deleteBucketResult = Result(Result::ErrorType::TRANSIENT_ERROR, "err1"); + f.hset.handler2.deleteBucketResult = Result(Result::ErrorType::PERMANENT_ERROR, "err2"); Result result = f.engine.deleteBucket(bucket1, context); - EXPECT_EQUAL(Result::PERMANENT_ERROR, result.getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::PERMANENT_ERROR, result.getErrorCode()); EXPECT_EQUAL("err1, err2", result.getErrorMessage()); } @@ -754,7 +754,7 @@ TEST_F("require that iterate requires valid iterator", SimpleFixture) { Context context(loadType, storage::spi::Priority(0), storage::spi::Trace::TraceLevel(0)); IterateResult it_result = f.engine.iterate(IteratorId(1), max_size, context); EXPECT_TRUE(it_result.hasError()); - EXPECT_EQUAL(Result::PERMANENT_ERROR, it_result.getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::PERMANENT_ERROR, it_result.getErrorCode()); EXPECT_EQUAL("Unknown iterator with id 1", it_result.getErrorMessage()); CreateIteratorResult result = @@ -799,7 +799,7 @@ TEST_F("require that destroyIterator prevents iteration", SimpleFixture) { uint64_t max_size = 1024; IterateResult it_result = f.engine.iterate(create_result.getIteratorId(), max_size, context); EXPECT_TRUE(it_result.hasError()); - EXPECT_EQUAL(Result::PERMANENT_ERROR, it_result.getErrorCode()); + EXPECT_EQUAL(Result::ErrorType::PERMANENT_ERROR, it_result.getErrorCode()); string msg_prefix = "Unknown iterator with id"; EXPECT_EQUAL(msg_prefix, it_result.getErrorMessage().substr(0, msg_prefix.size())); } diff --git a/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp b/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp index fb0c9b1f3a9..5c8ed28cd66 100644 --- a/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp +++ b/searchcore/src/tests/proton/server/disk_mem_usage_filter/disk_mem_usage_filter_test.cpp @@ -7,7 +7,7 @@ using namespace proton; -namespace fs = std::experimental::filesystem; +namespace fs = std::filesystem; struct Fixture { diff --git a/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp b/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp index b85e706397d..3047834be85 100644 --- a/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp +++ b/searchcore/src/vespa/searchcore/fdispatch/program/fdispatch.cpp @@ -271,7 +271,7 @@ CompressionConfig::Type convert(InternalFdispatchrcType::Packetcompresstype type) { switch (type) { - case InternalFdispatchrcType::LZ4: return CompressionConfig::LZ4; + case InternalFdispatchrcType::Packetcompresstype::LZ4: return CompressionConfig::LZ4; default: return CompressionConfig::LZ4; } } diff --git a/searchcore/src/vespa/searchcore/fdispatch/search/configdesc.h b/searchcore/src/vespa/searchcore/fdispatch/search/configdesc.h index 32f85e904ae..e0b0f0d7403 100644 --- a/searchcore/src/vespa/searchcore/fdispatch/search/configdesc.h +++ b/searchcore/src/vespa/searchcore/fdispatch/search/configdesc.h @@ -69,9 +69,9 @@ public: class QueryDistributionMode { public: enum Mode { - RANDOM = PartitionsConfig::Dataset::RANDOM, - AUTOMATIC = PartitionsConfig::Dataset::AUTOMATIC, - FIXEDROW = PartitionsConfig::Dataset::FIXEDROW + RANDOM = static_cast<int>(PartitionsConfig::Dataset::Querydistribution::RANDOM), + AUTOMATIC = static_cast<int>(PartitionsConfig::Dataset::Querydistribution::AUTOMATIC), + FIXEDROW = static_cast<int>(PartitionsConfig::Dataset::Querydistribution::FIXEDROW) }; QueryDistributionMode(Mode mode, double minGroupCoverage, double latencyDecayRate) : diff --git a/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt index 84d1cfb471a..4db36039b3c 100644 --- a/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt @@ -22,5 +22,5 @@ vespa_add_library(searchcore_pcommon STATIC DEPENDS searchcore_proton_metrics searchcore_fconfig - stdc++fs + ${VESPA_STDCXX_FS_LIB} ) diff --git a/searchcore/src/vespa/searchcore/proton/common/hw_info_sampler.cpp b/searchcore/src/vespa/searchcore/proton/common/hw_info_sampler.cpp index 1492a34b241..642d5a40ac7 100644 --- a/searchcore/src/vespa/searchcore/proton/common/hw_info_sampler.cpp +++ b/searchcore/src/vespa/searchcore/proton/common/hw_info_sampler.cpp @@ -8,7 +8,7 @@ #include <vespa/fastos/file.h> #include <vespa/searchcore/config/config-hwinfo.h> #include <vespa/vespalib/io/fileutil.h> -#include <experimental/filesystem> +#include <filesystem> #include <thread> #include <vespa/log/log.h> LOG_SETUP(".proton.common.hw_info_sampler"); @@ -32,8 +32,8 @@ sampleDiskSizeBytes(const std::string &pathStr, const HwInfoSampler::Config &cfg if (cfg.diskSizeBytes != 0) { return cfg.diskSizeBytes; } - std::experimental::filesystem::path path(pathStr); - auto space_info = std::experimental::filesystem::space(path); + std::filesystem::path path(pathStr); + auto space_info = std::filesystem::space(path); return space_info.capacity; } diff --git a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp index 57e26f77cca..1f862b07048 100644 --- a/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp +++ b/searchcore/src/vespa/searchcore/proton/persistenceengine/persistenceengine.cpp @@ -325,7 +325,7 @@ PersistenceEngine::put(const Bucket& b, Timestamp t, const document::Document::S if (!_writeFilter.acceptWriteOperation()) { IResourceWriteFilter::State state = _writeFilter.getAcceptState(); if (!state.acceptWriteOperation()) { - return Result(Result::RESOURCE_EXHAUSTED, + return Result(Result::ErrorType::RESOURCE_EXHAUSTED, make_string("Put operation rejected for document '%s': '%s'", doc->getId().toString().c_str(), state.message().c_str())); } @@ -335,12 +335,12 @@ PersistenceEngine::put(const Bucket& b, Timestamp t, const document::Document::S LOG(spam, "put(%s, %" PRIu64 ", (\"%s\", \"%s\"))", b.toString().c_str(), static_cast<uint64_t>(t.getValue()), docType.toString().c_str(), doc->getId().toString().c_str()); if (!doc->getId().hasDocType()) { - return Result(Result::PERMANENT_ERROR, + return Result(Result::ErrorType::PERMANENT_ERROR, make_string("Old id scheme not supported in elastic mode (%s)", doc->getId().toString().c_str())); } IPersistenceHandler::SP handler = getHandler(b.getBucketSpace(), docType); if (!handler) { - return Result(Result::PERMANENT_ERROR, + return Result(Result::ErrorType::PERMANENT_ERROR, make_string("No handler for document type '%s'", docType.toString().c_str())); } TransportLatch latch(1); @@ -356,13 +356,13 @@ PersistenceEngine::remove(const Bucket& b, Timestamp t, const DocumentId& did, C LOG(spam, "remove(%s, %" PRIu64 ", \"%s\")", b.toString().c_str(), static_cast<uint64_t>(t.getValue()), did.toString().c_str()); if (!did.hasDocType()) { - return RemoveResult(Result::PERMANENT_ERROR, + return RemoveResult(Result::ErrorType::PERMANENT_ERROR, make_string("Old id scheme not supported in elastic mode (%s)", did.toString().c_str())); } DocTypeName docType(did.getDocType()); IPersistenceHandler::SP handler = getHandler(b.getBucketSpace(), docType); if (!handler) { - return RemoveResult(Result::PERMANENT_ERROR, + return RemoveResult(Result::ErrorType::PERMANENT_ERROR, make_string("No handler for document type '%s'", docType.toString().c_str())); } TransportLatch latch(1); @@ -378,7 +378,7 @@ PersistenceEngine::update(const Bucket& b, Timestamp t, const DocumentUpdate::SP if (!_writeFilter.acceptWriteOperation()) { IResourceWriteFilter::State state = _writeFilter.getAcceptState(); if (!state.acceptWriteOperation()) { - return UpdateResult(Result::RESOURCE_EXHAUSTED, + return UpdateResult(Result::ErrorType::RESOURCE_EXHAUSTED, make_string("Update operation rejected for document '%s': '%s'", upd->getId().toString().c_str(), state.message().c_str())); } @@ -386,16 +386,16 @@ PersistenceEngine::update(const Bucket& b, Timestamp t, const DocumentUpdate::SP try { upd->eagerDeserialize(); } catch (document::FieldNotFoundException & e) { - return UpdateResult(Result::TRANSIENT_ERROR, + return UpdateResult(Result::ErrorType::TRANSIENT_ERROR, make_string("Update operation rejected for document '%s' of type '%s': 'Field not found'", upd->getId().toString().c_str(), upd->getType().getName().c_str())); } catch (document::DocumentTypeNotFoundException & e) { - return UpdateResult(Result::TRANSIENT_ERROR, + return UpdateResult(Result::ErrorType::TRANSIENT_ERROR, make_string("Update operation rejected for document '%s' of type '%s'.", upd->getId().toString().c_str(), e.getDocumentTypeName().c_str())); } catch (document::WrongTensorTypeException &e) { - return UpdateResult(Result::TRANSIENT_ERROR, + return UpdateResult(Result::ErrorType::TRANSIENT_ERROR, make_string("Update operation rejected for document '%s' of type '%s': 'Wrong tensor type: %s'", upd->getId().toString().c_str(), upd->getType().getName().c_str(), @@ -407,11 +407,11 @@ PersistenceEngine::update(const Bucket& b, Timestamp t, const DocumentUpdate::SP b.toString().c_str(), static_cast<uint64_t>(t.getValue()), docType.toString().c_str(), upd->getId().toString().c_str(), (upd->getCreateIfNonExistent() ? "true" : "false")); if (!upd->getId().hasDocType()) { - return UpdateResult(Result::PERMANENT_ERROR, + return UpdateResult(Result::ErrorType::PERMANENT_ERROR, make_string("Old id scheme not supported in elastic mode (%s)", upd->getId().toString().c_str())); } if (upd->getId().getDocType() != docType.getName()) { - return UpdateResult(Result::PERMANENT_ERROR, + return UpdateResult(Result::ErrorType::PERMANENT_ERROR, make_string("Update operation rejected due to bad id (%s, %s)", upd->getId().toString().c_str(), docType.getName().c_str())); } IPersistenceHandler::SP handler = getHandler(b.getBucketSpace(), docType); @@ -423,7 +423,7 @@ PersistenceEngine::update(const Bucket& b, Timestamp t, const DocumentUpdate::SP latch.await(); return latch.getUpdateResult(); } else { - return UpdateResult(Result::PERMANENT_ERROR, make_string("No handler for document type '%s'", docType.toString().c_str())); + return UpdateResult(Result::ErrorType::PERMANENT_ERROR, make_string("No handler for document type '%s'", docType.toString().c_str())); } } @@ -493,11 +493,11 @@ PersistenceEngine::iterate(IteratorId id, uint64_t maxByteSize, Context&) const std::lock_guard<std::mutex> guard(_iterators_lock); auto it = _iterators.find(id); if (it == _iterators.end()) { - return IterateResult(Result::PERMANENT_ERROR, make_string("Unknown iterator with id %" PRIu64, id.getValue())); + return IterateResult(Result::ErrorType::PERMANENT_ERROR, make_string("Unknown iterator with id %" PRIu64, id.getValue())); } iteratorEntry = it->second; if (iteratorEntry->in_use) { - return IterateResult(Result::TRANSIENT_ERROR, make_string("Iterator with id %" PRIu64 " is already in use", id.getValue())); + return IterateResult(Result::ErrorType::TRANSIENT_ERROR, make_string("Iterator with id %" PRIu64 " is already in use", id.getValue())); } iteratorEntry->in_use = true; } @@ -509,7 +509,7 @@ PersistenceEngine::iterate(IteratorId id, uint64_t maxByteSize, Context&) const iteratorEntry->in_use = false; return result; } catch (const std::exception & e) { - IterateResult result(Result::PERMANENT_ERROR, make_string("Caught exception during visitor iterator.iterate() = '%s'", e.what())); + IterateResult result(Result::ErrorType::PERMANENT_ERROR, make_string("Caught exception during visitor iterator.iterate() = '%s'", e.what())); LOG(warning, "Caught exception during visitor iterator.iterate() = '%s'", e.what()); std::lock_guard<std::mutex> guard(_iterators_lock); iteratorEntry->in_use = false; @@ -528,7 +528,7 @@ PersistenceEngine::destroyIterator(IteratorId id, Context&) return Result(); } if (it->second->in_use) { - return Result(Result::TRANSIENT_ERROR, make_string("Iterator with id %" PRIu64 " is currently in use", id.getValue())); + return Result(Result::ErrorType::TRANSIENT_ERROR, make_string("Iterator with id %" PRIu64 " is currently in use", id.getValue())); } delete it->second; _iterators.erase(it); diff --git a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt index d47e87e9e03..92cf186f697 100644 --- a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt @@ -121,5 +121,5 @@ vespa_add_library(searchcore_server STATIC searchcore_fconfig searchcore_reference configdefinitions - stdc++fs + ${VESPA_STDCXX_FS_LIB} ) diff --git a/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp b/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp index af9038cba66..1d3b2165c80 100644 --- a/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp @@ -26,7 +26,7 @@ BucketHandler::performSetCurrentState(BucketId bucketId, IGenericResultHandler *resultHandler) { if (!_nodeUp) { - Result result(Result::TRANSIENT_ERROR, + Result result(Result::ErrorType::TRANSIENT_ERROR, "Cannot set bucket active state when node is down"); resultHandler->handle(result); return; diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h index 982e3bba6fd..b9047d47793 100644 --- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h +++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_filter.h @@ -8,7 +8,7 @@ #include <vespa/searchcore/proton/persistenceengine/i_resource_write_filter.h> #include <vespa/vespalib/util/process_memory_stats.h> #include <atomic> -#include <experimental/filesystem> +#include <filesystem> #include <mutex> namespace proton { @@ -21,7 +21,7 @@ namespace proton { class DiskMemUsageFilter : public IResourceWriteFilter, public IDiskMemUsageNotifier { public: - using space_info = std::experimental::filesystem::space_info; + using space_info = std::filesystem::space_info; using Mutex = std::mutex; using Guard = std::lock_guard<Mutex>; diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.cpp b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.cpp index 7ef8c53d4dd..37f1664841a 100644 --- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.cpp @@ -3,7 +3,7 @@ #include "disk_mem_usage_sampler.h" #include <vespa/vespalib/util/timer.h> #include <vespa/vespalib/util/lambdatask.h> -#include <experimental/filesystem> +#include <filesystem> #include <unistd.h> using vespalib::makeLambdaTask; @@ -47,7 +47,7 @@ DiskMemUsageSampler::sampleUsage() namespace { -namespace fs = std::experimental::filesystem; +namespace fs = std::filesystem; uint64_t sampleDiskUsageOnFileSystem(const fs::path &path, const HwInfo::Disk &disk) diff --git a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.h b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.h index 4ed48613f6a..5a439e69003 100644 --- a/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.h +++ b/searchcore/src/vespa/searchcore/proton/server/disk_mem_usage_sampler.h @@ -13,7 +13,7 @@ namespace proton { */ class DiskMemUsageSampler { DiskMemUsageFilter _filter; - std::experimental::filesystem::path _path; + std::filesystem::path _path; double _sampleInterval; std::unique_ptr<vespalib::Timer> _periodicTimer; diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp index 53f7f544980..a562408b64d 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp @@ -152,9 +152,9 @@ template<typename T> CompressionConfig deriveCompression(const T & config) { CompressionConfig compression; - if (config.type == T::LZ4) { + if (config.type == T::Type::LZ4) { compression.type = CompressionConfig::LZ4; - } else if (config.type == T::ZSTD) { + } else if (config.type == T::Type::ZSTD) { compression.type = CompressionConfig::ZSTD; } compression.compressionLevel = config.level; diff --git a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp index fd38b74f584..338fc738040 100644 --- a/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/feedhandler.cpp @@ -476,7 +476,7 @@ void feedOperationRejected(FeedToken & token, const vespalib::string &opType, co if (token) { auto message = make_string("%s operation rejected for document '%s' of type '%s': '%s'", opType.c_str(), docId.c_str(), docTypeName.toString().c_str(), rejectMessage.c_str()); - token->setResult(make_unique<ResultType>(Result::RESOURCE_EXHAUSTED, message), false); + token->setResult(make_unique<ResultType>(Result::ErrorType::RESOURCE_EXHAUSTED, message), false); token->fail(); } } @@ -527,7 +527,7 @@ FeedHandler::considerUpdateOperationForRejection(FeedToken &token, UpdateOperati if (token) { auto message = make_string("Update operation rejected for document '%s' of type '%s': 'Field not found'", update.getId().toString().c_str(), _docTypeName.toString().c_str()); - token->setResult(make_unique<UpdateResult>(Result::TRANSIENT_ERROR, message), false); + token->setResult(make_unique<UpdateResult>(Result::ErrorType::TRANSIENT_ERROR, message), false); token->fail(); } return true; @@ -536,7 +536,7 @@ FeedHandler::considerUpdateOperationForRejection(FeedToken &token, UpdateOperati update.getId().toString().c_str(), e.getDocumentTypeName().c_str(), _docTypeName.toString().c_str()); - token->setResult(make_unique<UpdateResult>(Result::TRANSIENT_ERROR, message), false); + token->setResult(make_unique<UpdateResult>(Result::ErrorType::TRANSIENT_ERROR, message), false); token->fail(); return true; } catch (document::WrongTensorTypeException &e) { @@ -544,7 +544,7 @@ FeedHandler::considerUpdateOperationForRejection(FeedToken &token, UpdateOperati update.getId().toString().c_str(), _docTypeName.toString().c_str(), e.getMessage().c_str()); - token->setResult(make_unique<UpdateResult>(Result::TRANSIENT_ERROR, message), false); + token->setResult(make_unique<UpdateResult>(Result::ErrorType::TRANSIENT_ERROR, message), false); token->fail(); return true; } diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.cpp b/searchcore/src/vespa/searchcore/proton/server/proton.cpp index 06f19eb06cc..5db499601f3 100644 --- a/searchcore/src/vespa/searchcore/proton/server/proton.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/proton.cpp @@ -65,7 +65,7 @@ CompressionConfig::Type convert(InternalProtonType::Packetcompresstype type) { switch (type) { - case InternalProtonType::LZ4: return CompressionConfig::LZ4; + case InternalProtonType::Packetcompresstype::LZ4: return CompressionConfig::LZ4; default: return CompressionConfig::LZ4; } } @@ -74,10 +74,10 @@ void setBucketCheckSumType(const ProtonConfig & proton) { switch (proton.bucketdb.checksumtype) { - case InternalProtonType::Bucketdb::LEGACY: + case InternalProtonType::Bucketdb::Checksumtype::LEGACY: bucketdb::BucketState::setChecksumType(bucketdb::BucketState::ChecksumType::LEGACY); break; - case InternalProtonType::Bucketdb::XXHASH64: + case InternalProtonType::Bucketdb::Checksumtype::XXHASH64: bucketdb::BucketState::setChecksumType(bucketdb::BucketState::ChecksumType::XXHASH64); break; } @@ -273,7 +273,7 @@ Proton::init(const BootstrapConfig::SP & configSnapshot) IFlushStrategy::SP strategy; const ProtonConfig::Flush & flush(protonConfig.flush); switch (flush.strategy) { - case ProtonConfig::Flush::MEMORY: { + case ProtonConfig::Flush::Strategy::MEMORY: { auto memoryFlush = std::make_shared<MemoryFlush>( MemoryFlushConfigUpdater::convertConfig(flush.memory, hwInfo.memory()), fastos::ClockSystem::now()); _memoryFlushConfigUpdater = std::make_unique<MemoryFlushConfigUpdater>(memoryFlush, flush.memory, hwInfo.memory()); @@ -281,7 +281,7 @@ Proton::init(const BootstrapConfig::SP & configSnapshot) strategy = memoryFlush; break; } - case ProtonConfig::Flush::SIMPLE: + case ProtonConfig::Flush::Strategy::SIMPLE: default: strategy = std::make_shared<SimpleFlush>(); break; diff --git a/searchcore/src/vespa/searchcore/proton/server/searchview.h b/searchcore/src/vespa/searchcore/proton/server/searchview.h index 186d6154706..28c7ffd2b36 100644 --- a/searchcore/src/vespa/searchcore/proton/server/searchview.h +++ b/searchcore/src/vespa/searchcore/proton/server/searchview.h @@ -17,8 +17,10 @@ public: typedef std::shared_ptr<SearchView> SP; SearchView(const ISummaryManager::ISummarySetup::SP &summarySetup, const MatchView::SP &matchView); - SearchView(SearchView &&) = default; - SearchView &operator=(SearchView &&) = default; + SearchView(const SearchView &) = delete; + SearchView(SearchView &&) = delete; + SearchView &operator=(const SearchView &) = delete; + SearchView &operator=(SearchView &&) = delete; ~SearchView(); const ISummaryManager::ISummarySetup::SP & getSummarySetup() const { return _summarySetup; } diff --git a/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp b/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp index 09f89494019..62cde4d2c9c 100644 --- a/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp +++ b/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp @@ -222,27 +222,29 @@ AttributeManagerTest::testConfigConvert() // typedef AttributeVector::Config AVC; typedef BT AVBT; typedef CollectionType AVCT; - typedef AttributesConfig::Attribute CACA; + using CACA = AttributesConfig::Attribute; + using CACAD = CACA::Datatype; + using CACAC = CACA::Collectiontype; typedef ConfigConverter CC; - EXPECT_TRUE(assertDataType(AVBT::STRING, CACA::STRING)); - EXPECT_TRUE(assertDataType(AVBT::INT8, CACA::INT8)); - EXPECT_TRUE(assertDataType(AVBT::INT16, CACA::INT16)); - EXPECT_TRUE(assertDataType(AVBT::INT32, CACA::INT32)); - EXPECT_TRUE(assertDataType(AVBT::INT64, CACA::INT64)); - EXPECT_TRUE(assertDataType(AVBT::FLOAT, CACA::FLOAT)); - EXPECT_TRUE(assertDataType(AVBT::DOUBLE, CACA::DOUBLE)); - EXPECT_TRUE(assertDataType(AVBT::PREDICATE, CACA::PREDICATE)); - EXPECT_TRUE(assertDataType(AVBT::TENSOR, CACA::TENSOR)); - EXPECT_TRUE(assertDataType(AVBT::NONE, CACA::NONE)); - - EXPECT_TRUE(assertCollectionType(AVCT::SINGLE, CACA::SINGLE)); - EXPECT_TRUE(assertCollectionType(AVCT::ARRAY, CACA::ARRAY)); - EXPECT_TRUE(assertCollectionType(AVCT::WSET, CACA::WEIGHTEDSET)); + EXPECT_TRUE(assertDataType(AVBT::STRING, CACAD::STRING)); + EXPECT_TRUE(assertDataType(AVBT::INT8, CACAD::INT8)); + EXPECT_TRUE(assertDataType(AVBT::INT16, CACAD::INT16)); + EXPECT_TRUE(assertDataType(AVBT::INT32, CACAD::INT32)); + EXPECT_TRUE(assertDataType(AVBT::INT64, CACAD::INT64)); + EXPECT_TRUE(assertDataType(AVBT::FLOAT, CACAD::FLOAT)); + EXPECT_TRUE(assertDataType(AVBT::DOUBLE, CACAD::DOUBLE)); + EXPECT_TRUE(assertDataType(AVBT::PREDICATE, CACAD::PREDICATE)); + EXPECT_TRUE(assertDataType(AVBT::TENSOR, CACAD::TENSOR)); + EXPECT_TRUE(assertDataType(AVBT::NONE, CACAD::NONE)); + + EXPECT_TRUE(assertCollectionType(AVCT::SINGLE, CACAC::SINGLE)); + EXPECT_TRUE(assertCollectionType(AVCT::ARRAY, CACAC::ARRAY)); + EXPECT_TRUE(assertCollectionType(AVCT::WSET, CACAC::WEIGHTEDSET)); EXPECT_TRUE(assertCollectionType(AVCT(AVCT::SINGLE, true, false), - CACA::SINGLE, true, false)); + CACAC::SINGLE, true, false)); EXPECT_TRUE(assertCollectionType(AVCT(AVCT::SINGLE, false, true), - CACA::SINGLE, false, true)); + CACAC::SINGLE, false, true)); { // fastsearch CACA a; @@ -270,7 +272,7 @@ AttributeManagerTest::testConfigConvert() } { // tensor CACA a; - a.datatype = CACA::TENSOR; + a.datatype = CACAD::TENSOR; a.tensortype = "tensor(x[5])"; AttributeVector::Config out = ConfigConverter::convert(a); EXPECT_EQUAL("tensor(x[5])", out.tensorType().to_spec()); diff --git a/searchlib/src/tests/memoryindex/field_index_remover/field_index_remover_test.cpp b/searchlib/src/tests/memoryindex/field_index_remover/field_index_remover_test.cpp index c0e8871b80a..f11df42f6b1 100644 --- a/searchlib/src/tests/memoryindex/field_index_remover/field_index_remover_test.cpp +++ b/searchlib/src/tests/memoryindex/field_index_remover/field_index_remover_test.cpp @@ -19,7 +19,7 @@ struct WordFieldPair { WordFieldPair(vespalib::stringref word, uint32_t fieldId) : _word(word), _fieldId(fieldId) {} - bool operator<(const WordFieldPair &rhs) { + bool operator<(const WordFieldPair &rhs) const { if (_word != rhs._word) { return _word < rhs._word; } diff --git a/searchlib/src/tests/queryeval/queryeval.cpp b/searchlib/src/tests/queryeval/queryeval.cpp index db7f8d1cda1..56c6f7e1282 100644 --- a/searchlib/src/tests/queryeval/queryeval.cpp +++ b/searchlib/src/tests/queryeval/queryeval.cpp @@ -575,7 +575,14 @@ getExpectedSlime() { } TEST("testDump") { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshadow" +#endif typedef SourceBlenderSearch::Child Source; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif SearchIterator::UP search( AndSearch::create( Collect<SearchIterator*, MultiSearch::Children>() diff --git a/searchlib/src/vespa/searchlib/CMakeLists.txt b/searchlib/src/vespa/searchlib/CMakeLists.txt index e4e1f92898f..f6f9ad2259a 100644 --- a/searchlib/src/vespa/searchlib/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/CMakeLists.txt @@ -32,7 +32,7 @@ vespa_add_library(searchlib INSTALL lib64 DEPENDS staging_vespalib - atomic + ${VESPA_ATOMIC_LIB} ) vespa_add_target_package_dependency(searchlib Protobuf) diff --git a/searchlib/src/vespa/searchlib/attribute/configconverter.cpp b/searchlib/src/vespa/searchlib/attribute/configconverter.cpp index ae08204f5bc..535e81fc032 100644 --- a/searchlib/src/vespa/searchlib/attribute/configconverter.cpp +++ b/searchlib/src/vespa/searchlib/attribute/configconverter.cpp @@ -19,20 +19,20 @@ DataTypeMap getDataTypeMap() { DataTypeMap map; - map[AttributesConfig::Attribute::STRING] = BasicType::STRING; - map[AttributesConfig::Attribute::BOOL] = BasicType::BOOL; - map[AttributesConfig::Attribute::UINT2] = BasicType::UINT2; - map[AttributesConfig::Attribute::UINT4] = BasicType::UINT4; - map[AttributesConfig::Attribute::INT8] = BasicType::INT8; - map[AttributesConfig::Attribute::INT16] = BasicType::INT16; - map[AttributesConfig::Attribute::INT32] = BasicType::INT32; - map[AttributesConfig::Attribute::INT64] = BasicType::INT64; - map[AttributesConfig::Attribute::FLOAT] = BasicType::FLOAT; - map[AttributesConfig::Attribute::DOUBLE] = BasicType::DOUBLE; - map[AttributesConfig::Attribute::PREDICATE] = BasicType::PREDICATE; - map[AttributesConfig::Attribute::TENSOR] = BasicType::TENSOR; - map[AttributesConfig::Attribute::REFERENCE] = BasicType::REFERENCE; - map[AttributesConfig::Attribute::NONE] = BasicType::NONE; + map[AttributesConfig::Attribute::Datatype::STRING] = BasicType::STRING; + map[AttributesConfig::Attribute::Datatype::BOOL] = BasicType::BOOL; + map[AttributesConfig::Attribute::Datatype::UINT2] = BasicType::UINT2; + map[AttributesConfig::Attribute::Datatype::UINT4] = BasicType::UINT4; + map[AttributesConfig::Attribute::Datatype::INT8] = BasicType::INT8; + map[AttributesConfig::Attribute::Datatype::INT16] = BasicType::INT16; + map[AttributesConfig::Attribute::Datatype::INT32] = BasicType::INT32; + map[AttributesConfig::Attribute::Datatype::INT64] = BasicType::INT64; + map[AttributesConfig::Attribute::Datatype::FLOAT] = BasicType::FLOAT; + map[AttributesConfig::Attribute::Datatype::DOUBLE] = BasicType::DOUBLE; + map[AttributesConfig::Attribute::Datatype::PREDICATE] = BasicType::PREDICATE; + map[AttributesConfig::Attribute::Datatype::TENSOR] = BasicType::TENSOR; + map[AttributesConfig::Attribute::Datatype::REFERENCE] = BasicType::REFERENCE; + map[AttributesConfig::Attribute::Datatype::NONE] = BasicType::NONE; return map; } @@ -40,9 +40,9 @@ CollectionTypeMap getCollectionTypeMap() { CollectionTypeMap map; - map[AttributesConfig::Attribute::SINGLE] = CollectionType::SINGLE; - map[AttributesConfig::Attribute::ARRAY] = CollectionType::ARRAY; - map[AttributesConfig::Attribute::WEIGHTEDSET] = CollectionType::WSET; + map[AttributesConfig::Attribute::Collectiontype::SINGLE] = CollectionType::SINGLE; + map[AttributesConfig::Attribute::Collectiontype::ARRAY] = CollectionType::ARRAY; + map[AttributesConfig::Attribute::Collectiontype::WEIGHTEDSET] = CollectionType::WSET; return map; } diff --git a/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp index 075eaceb11b..047de7de3c7 100644 --- a/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp +++ b/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp @@ -101,13 +101,18 @@ EnumAttribute<B>::insertNewUniqueValues(EnumStoreBase::IndexVector & newIndexes) do { // perform compaction on EnumStore if necessary if (extraBytesNeeded > this->_enumStore.getRemaining() || - this->_enumStore.getPendingCompact()) { + this->_enumStore.getPendingCompact()) + { + this->logEnumStoreEvent("enumstorecompact", "reserve"); this->removeAllOldGenerations(); this->_enumStore.clearPendingCompact(); EnumIndexMap old2New(this->_enumStore.getNumUniques()*3); + this->logEnumStoreEvent("enumstorecompact", "start"); if (!this->_enumStore.performCompaction(extraBytesNeeded, old2New)) { + this->logEnumStoreEvent("enumstorecompact", "failed_compact"); // fallback to resize strategy this->_enumStore.fallbackResize(extraBytesNeeded); + this->logEnumStoreEvent("enumstorecompact", "fallbackresize_complete"); if (extraBytesNeeded > this->_enumStore.getRemaining()) { HDR_ABORT("Cannot fallbackResize enumStore"); } @@ -120,6 +125,7 @@ EnumAttribute<B>::insertNewUniqueValues(EnumStoreBase::IndexVector & newIndexes) for (auto & data : this->_changes) { data._enumScratchPad = ChangeBase::UNSET_ENUM; } + this->logEnumStoreEvent("enumstorecompact", "complete"); } } while (0); diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp index eb4d5fadd2a..4460d1757a2 100644 --- a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp +++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp @@ -11,7 +11,14 @@ namespace search::attribute { template <typename EntryT, typename RefT> MultiValueMapping<EntryT,RefT>::MultiValueMapping(const datastore::ArrayStoreConfig &storeCfg, const vespalib::GrowStrategy &gs) +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wuninitialized" +#endif : MultiValueMappingBase(gs, _store.getGenerationHolder()), +#ifdef __clang__ +#pragma clang diagnostic pop +#endif _store(storeCfg) { } diff --git a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp index 4f5cd3f4276..3c248342696 100644 --- a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp +++ b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp @@ -137,8 +137,10 @@ template <typename B> void SingleValueEnumAttribute<B>::reEnumerate(const EnumIndexMap & old2New) { + this->logEnumStoreEvent("reenumerate", "reserved"); auto newIndexes = std::make_unique<vespalib::Array<EnumIndex>>(); newIndexes->reserve(_enumIndices.capacity()); + this->logEnumStoreEvent("reenumerate", "start"); for (uint32_t i = 0; i < _enumIndices.size(); ++i) { EnumIndex oldIdx = _enumIndices[i]; EnumIndex newIdx; @@ -154,6 +156,7 @@ SingleValueEnumAttribute<B>::reEnumerate(const EnumIndexMap & old2New) _enumIndices.replaceVector(std::move(newIndexes)); } this->logEnumStoreEvent("compactfixup", "complete"); + this->logEnumStoreEvent("reenumerate", "complete"); } template <typename B> diff --git a/searchlib/src/vespa/searchlib/bitcompression/compression.h b/searchlib/src/vespa/searchlib/bitcompression/compression.h index de206d33b8d..350932263c3 100644 --- a/searchlib/src/vespa/searchlib/bitcompression/compression.h +++ b/searchlib/src/vespa/searchlib/bitcompression/compression.h @@ -1143,7 +1143,7 @@ public: DecodeContext64Base() : search::ComprFileDecodeContext(), _valI(nullptr), - _valE(static_cast<const uint64_t *>(nullptr) - 1), + _valE(reinterpret_cast<const uint64_t *>(PTRDIFF_MAX)), _realValE(nullptr), _val(0), _cacheInt(0), @@ -1325,7 +1325,7 @@ public: DecodeContext64(const uint64_t *compr, int bitOffset) : DecodeContext64Base(compr + 1, - static_cast<const uint64_t *>(nullptr) - 1, + reinterpret_cast<const uint64_t *>(PTRDIFF_MAX), nullptr, 0, EC::bswap(*compr), diff --git a/searchlib/src/vespa/searchlib/common/tunefileinfo.h b/searchlib/src/vespa/searchlib/common/tunefileinfo.h index d431a9cf78f..bcd6765845b 100644 --- a/searchlib/src/vespa/searchlib/common/tunefileinfo.h +++ b/searchlib/src/vespa/searchlib/common/tunefileinfo.h @@ -26,10 +26,10 @@ public: template <typename Config> void setFromConfig(const enum Config::Io &config) { switch (config) { - case Config::NORMAL: + case Config::Io::NORMAL: _tuneControl = NORMAL; break; - case Config::DIRECTIO: + case Config::Io::DIRECTIO: _tuneControl = DIRECTIO; break; default: @@ -70,13 +70,13 @@ public: template <typename Config> void setFromConfig(const enum Config::Io &config) { switch (config) { - case Config::NORMAL: + case Config::Io::NORMAL: _tuneControl = NORMAL; break; - case Config::OSYNC: + case Config::Io::OSYNC: _tuneControl = OSYNC; break; - case Config::DIRECTIO: + case Config::Io::DIRECTIO: _tuneControl = DIRECTIO; break; default: diff --git a/searchlib/src/vespa/searchlib/common/tunefileinfo.hpp b/searchlib/src/vespa/searchlib/common/tunefileinfo.hpp index 17d7949e9b9..5f28f82c526 100644 --- a/searchlib/src/vespa/searchlib/common/tunefileinfo.hpp +++ b/searchlib/src/vespa/searchlib/common/tunefileinfo.hpp @@ -12,9 +12,9 @@ template <typename TuneControlConfig, typename MMapConfig> void TuneFileRandRead::setFromConfig(const enum TuneControlConfig::Io & tuneControlConfig, const MMapConfig & mmapFlags) { switch ( tuneControlConfig) { - case TuneControlConfig::NORMAL: _tuneControl = NORMAL; break; - case TuneControlConfig::DIRECTIO: _tuneControl = DIRECTIO; break; - case TuneControlConfig::MMAP: _tuneControl = MMAP; break; + case TuneControlConfig::Io::NORMAL: _tuneControl = NORMAL; break; + case TuneControlConfig::Io::DIRECTIO: _tuneControl = DIRECTIO; break; + case TuneControlConfig::Io::MMAP: _tuneControl = MMAP; break; default: _tuneControl = NORMAL; break; } setFromMmapConfig(mmapFlags); @@ -25,15 +25,15 @@ void TuneFileRandRead::setFromMmapConfig(const MMapConfig & mmapFlags) { for (size_t i(0), m(mmapFlags.options.size()); i < m; i++) { switch (mmapFlags.options[i]) { - case MMapConfig::MLOCK: _mmapFlags |= MAP_LOCKED; break; - case MMapConfig::POPULATE: _mmapFlags |= MAP_POPULATE; break; - case MMapConfig::HUGETLB: _mmapFlags |= MAP_HUGETLB; break; + case MMapConfig::Options::MLOCK: _mmapFlags |= MAP_LOCKED; break; + case MMapConfig::Options::POPULATE: _mmapFlags |= MAP_POPULATE; break; + case MMapConfig::Options::HUGETLB: _mmapFlags |= MAP_HUGETLB; break; } } switch (mmapFlags.advise) { - case MMapConfig::NORMAL: setAdvise(POSIX_FADV_NORMAL); break; - case MMapConfig::RANDOM: setAdvise(POSIX_FADV_RANDOM); break; - case MMapConfig::SEQUENTIAL: setAdvise(POSIX_FADV_SEQUENTIAL); break; + case MMapConfig::Advise::NORMAL: setAdvise(POSIX_FADV_NORMAL); break; + case MMapConfig::Advise::RANDOM: setAdvise(POSIX_FADV_RANDOM); break; + case MMapConfig::Advise::SEQUENTIAL: setAdvise(POSIX_FADV_SEQUENTIAL); break; } } diff --git a/searchlib/src/vespa/searchlib/expression/functionnodes.cpp b/searchlib/src/vespa/searchlib/expression/functionnodes.cpp index 93655ec2925..0eb85cba4ba 100644 --- a/searchlib/src/vespa/searchlib/expression/functionnodes.cpp +++ b/searchlib/src/vespa/searchlib/expression/functionnodes.cpp @@ -212,7 +212,14 @@ void MultiArgFunctionNode::onPrepareResult() } else if (_args.size() > 1) { setResultType(std::unique_ptr<ResultNode>(static_cast<ResultNode *>(_args[0]->getResult().clone()))); for(size_t i(1), m(_args.size()); i < m; i++) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-undefined-compare" +#endif if (&_args[i]->getResult() != NULL) { +#ifdef __clang__ +#pragma clang diagnostic pop +#endif setResultType(_ArithmeticTypeConversion.getType(getResult(), _args[i]->getResult())); } } diff --git a/searchlib/src/vespa/searchlib/features/attributefeature.cpp b/searchlib/src/vespa/searchlib/features/attributefeature.cpp index 4fff5ae5b3f..b1d3356dd62 100644 --- a/searchlib/src/vespa/searchlib/features/attributefeature.cpp +++ b/searchlib/src/vespa/searchlib/features/attributefeature.cpp @@ -12,6 +12,7 @@ #include <vespa/searchlib/tensor/dense_tensor_attribute.h> #include <vespa/searchlib/fef/indexproperties.h> #include <vespa/searchlib/attribute/singlenumericattribute.h> +#include <vespa/searchlib/attribute/multinumericattribute.h> #include <vespa/log/log.h> LOG_SETUP(".features.attributefeature"); @@ -35,6 +36,7 @@ using search::fef::FeatureType; using namespace search::fef::indexproperties; +namespace search::features { namespace { template <typename X, typename Y> bool equals(X lhs, Y rhs) { @@ -52,17 +54,17 @@ isUndefined(T value, BasicType::Type type) { switch (type) { case BasicType::INT8: - return search::attribute::isUndefined<int8_t>(static_cast<int8_t>(value)); + return attribute::isUndefined<int8_t>(static_cast<int8_t>(value)); case BasicType::INT16: - return search::attribute::isUndefined<int16_t>(static_cast<int16_t>(value)); + return attribute::isUndefined<int16_t>(static_cast<int16_t>(value)); case BasicType::INT32: - return search::attribute::isUndefined<int32_t>(static_cast<int32_t>(value)); + return attribute::isUndefined<int32_t>(static_cast<int32_t>(value)); case BasicType::INT64: - return search::attribute::isUndefined<int64_t>(static_cast<int64_t>(value)); + return attribute::isUndefined<int64_t>(static_cast<int64_t>(value)); case BasicType::FLOAT: - return search::attribute::isUndefined<float>(static_cast<float>(value)); + return attribute::isUndefined<float>(static_cast<float>(value)); case BasicType::DOUBLE: - return search::attribute::isUndefined<double>(static_cast<double>(value)); + return attribute::isUndefined<double>(static_cast<double>(value)); default: return false; } @@ -76,28 +78,22 @@ isUndefined<vespalib::stringref>(vespalib::stringref, BasicType::Type) } template <typename T> -search::feature_t +feature_t considerUndefined(T value, BasicType::Type type) { if (isUndefined(value, type)) { - return search::attribute::getUndefined<search::feature_t>(); + return attribute::getUndefined<feature_t>(); } - return search::features::util::getAsFeature(value); + return util::getAsFeature(value); } template <> -search::feature_t +feature_t considerUndefined<ConstCharPtr>(ConstCharPtr value, BasicType::Type ) { - return search::features::util::getAsFeature(value); + return util::getAsFeature(value); } - -} - - -namespace search::features { - /** * Implements the executor for fetching values from a single or array attribute vector */ @@ -115,6 +111,24 @@ public: void execute(uint32_t docId) override; }; +/** + * Implements the executor for fetching values from a single or array attribute vector + */ +template <typename T> +class MultiAttributeExecutor : public fef::FeatureExecutor { +private: + const T & _attribute; + uint32_t _idx; +public: + /** + * Constructs an executor. + * + * @param attribute The attribute vector to use. + */ + MultiAttributeExecutor(const T & attribute, uint32_t idx) : _attribute(attribute), _idx(idx) { } + void execute(uint32_t docId) override; +}; + class CountOnlyAttributeExecutor : public fef::FeatureExecutor { private: const attribute::IAttributeVector & _attribute; @@ -182,21 +196,37 @@ SingleAttributeExecutor<T>::execute(uint32_t docId) { typename T::LoadedValueType v = _attribute.getFast(docId); // value - outputs().set_number(0, __builtin_expect(attribute::isUndefined(v), false) - ? attribute::getUndefined<feature_t>() - : util::getAsFeature(v)); - outputs().set_number(1, 0.0f); // weight - outputs().set_number(2, 0.0f); // contains - outputs().set_number(3, 1.0f); // count + auto o = outputs().get_bound(); + o[0].as_number = __builtin_expect(attribute::isUndefined(v), false) + ? attribute::getUndefined<feature_t>() + : util::getAsFeature(v); + o[1].as_number = 0; // weight + o[2].as_number = 0; // contains + o[3].as_number = 1; // contains +} + +template <typename T> +void +MultiAttributeExecutor<T>::execute(uint32_t docId) +{ + const multivalue::Value<typename T::BaseType> * values = nullptr; + uint32_t numValues = _attribute.getRawValues(docId, values); + + auto o = outputs().get_bound(); + o[0].as_number = __builtin_expect(_idx < numValues, true) ? values[_idx].value() : 0; + o[1].as_number = 0; // weight + o[2].as_number = 0; // contains + o[3].as_number = 0; // count } void CountOnlyAttributeExecutor::execute(uint32_t docId) { - outputs().set_number(0, 0.0f); // value - outputs().set_number(1, 0.0f); // weight - outputs().set_number(2, 0.0f); // contains - outputs().set_number(3, _attribute.getValueCount(docId)); // count + auto o = outputs().get_bound(); + o[0].as_number = 0; // value + o[1].as_number = 0; // weight + o[2].as_number = 0; // contains + o[3].as_number = _attribute.getValueCount(docId); // count } template <typename T> @@ -220,10 +250,11 @@ AttributeExecutor<T>::execute(uint32_t docId) if (_idx < _buffer.size()) { value = considerUndefined(_buffer[_idx], _attrType); } - outputs().set_number(0, value); // value - outputs().set_number(1, 0.0f); // weight - outputs().set_number(2, 0.0f); // contains - outputs().set_number(3, _defaultCount); // count + auto o = outputs().get_bound(); + o[0].as_number = value; // value + o[1].as_number = 0; // weight + o[2].as_number = 0; // contains + o[3].as_number = _defaultCount; // count } @@ -265,70 +296,39 @@ WeightedSetAttributeExecutor<BT, T>::execute(uint32_t docId) outputs().set_number(3, count); // count } - -AttributeBlueprint::AttributeBlueprint() : - fef::Blueprint("attribute"), - _attrName(), - _attrKey(), - _extra(), - _tensorType(ValueType::double_type()) -{ -} - -AttributeBlueprint::~AttributeBlueprint() = default; - -void -AttributeBlueprint::visitDumpFeatures(const fef::IIndexEnvironment &, - fef::IDumpFeatureVisitor &) const -{ -} - -bool -AttributeBlueprint::setup(const fef::IIndexEnvironment & env, - const fef::ParameterList & params) -{ - // params[0] = attribute name - // params[1] = index (array attribute) or key (weighted set attribute) - _attrName = params[0].getValue(); - _attrKey = createAttributeKey(_attrName); - if (params.size() == 2) { - _extra = params[1].getValue(); - } - vespalib::string attrType = type::Attribute::lookup(env.getProperties(), _attrName); - if (!attrType.empty()) { - _tensorType = ValueType::from_spec(attrType); - if (_tensorType.is_error()) { - LOG(error, "%s: invalid type: '%s'", getName().c_str(), attrType.c_str()); - } +template <typename T> +struct SingleValueExecutorCreator { + using AttrType = SingleValueNumericAttribute<T>; + using PtrType = const AttrType *; + using ExecType = SingleAttributeExecutor<AttrType>; + SingleValueExecutorCreator() : ptr(nullptr) {} + bool handle(const IAttributeVector *attribute) { + ptr = dynamic_cast<PtrType>(attribute); + return ptr != nullptr; } - FeatureType output_type = _tensorType.is_double() - ? FeatureType::number() - : FeatureType::object(_tensorType); - describeOutput("value", "The value of a single value attribute, " - "the value at the given index of an array attribute, " - "the given key of a weighted set attribute, or" - "the tensor of a tensor attribute", output_type); - if (!_tensorType.is_tensor()) { - describeOutput("weight", "The weight associated with the given key in a weighted set attribute."); - describeOutput("contains", "1 if the given key is present in a weighted set attribute, 0 otherwise."); - describeOutput("count", "Returns the number of elements in this array or weighted set attribute."); + fef::FeatureExecutor & create(vespalib::Stash &stash) const { + return stash.create<ExecType>(*ptr); } - env.hintAttributeAccess(_attrName); - return !_tensorType.is_error(); -} - -fef::Blueprint::UP -AttributeBlueprint::createInstance() const -{ - return std::make_unique<AttributeBlueprint>(); -} +private: + PtrType ptr; +}; -#define CREATE_AND_RETURN_IF_SINGLE_NUMERIC(a, T) \ - if (dynamic_cast<const SingleValueNumericAttribute<T> *>(a) != nullptr) { \ - return stash.create<SingleAttributeExecutor<SingleValueNumericAttribute<T>>>(*static_cast<const SingleValueNumericAttribute<T> *>(a)); \ +template <typename T> +struct MultiValueExecutorCreator { + using AttrType = MultiValueNumericAttribute<T, multivalue::Value<typename T::BaseType>>; + using PtrType = const AttrType *; + using ExecType = MultiAttributeExecutor<AttrType>; + MultiValueExecutorCreator() : ptr(nullptr) {} + bool handle(const IAttributeVector *attribute) { + ptr = dynamic_cast<PtrType>(attribute); + return ptr != nullptr; } - -namespace { + fef::FeatureExecutor & create(vespalib::Stash &stash, uint32_t idx) const { + return stash.create<ExecType>(*ptr, idx); + } +private: + PtrType ptr; +}; fef::FeatureExecutor & createAttributeExecutor(const IAttributeVector *attribute, const vespalib::string &attrName, const vespalib::string &extraParam, vespalib::Stash &stash) @@ -354,10 +354,10 @@ createAttributeExecutor(const IAttributeVector *attribute, const vespalib::strin } } else { // SINGLE or ARRAY if ((attribute->getCollectionType() == CollectionType::SINGLE) && (attribute->isIntegerType() || attribute->isFloatingPointType())) { - CREATE_AND_RETURN_IF_SINGLE_NUMERIC(attribute, FloatingPointAttributeTemplate<double>); - CREATE_AND_RETURN_IF_SINGLE_NUMERIC(attribute, FloatingPointAttributeTemplate<float>); - CREATE_AND_RETURN_IF_SINGLE_NUMERIC(attribute, IntegerAttributeTemplate<int32_t>); - CREATE_AND_RETURN_IF_SINGLE_NUMERIC(attribute, IntegerAttributeTemplate<int64_t>); + { SingleValueExecutorCreator<FloatingPointAttributeTemplate<double>> creator; if (creator.handle(attribute)) return creator.create(stash); } + { SingleValueExecutorCreator<FloatingPointAttributeTemplate<float>> creator; if (creator.handle(attribute)) return creator.create(stash); } + { SingleValueExecutorCreator<IntegerAttributeTemplate<int32_t>> creator; if (creator.handle(attribute)) return creator.create(stash); } + { SingleValueExecutorCreator<IntegerAttributeTemplate<int64_t>> creator; if (creator.handle(attribute)) return creator.create(stash); } } { uint32_t idx = 0; @@ -369,8 +369,12 @@ createAttributeExecutor(const IAttributeVector *attribute, const vespalib::strin if (attribute->isStringType()) { return stash.create<AttributeExecutor<ConstCharContent>>(attribute, idx); } else if (attribute->isIntegerType()) { + { MultiValueExecutorCreator<IntegerAttributeTemplate<int32_t>> creator; if (creator.handle(attribute)) return creator.create(stash, idx); } + { MultiValueExecutorCreator<IntegerAttributeTemplate<int64_t>> creator; if (creator.handle(attribute)) return creator.create(stash, idx); } return stash.create<AttributeExecutor<IntegerContent>>(attribute, idx); } else { // FLOAT + { MultiValueExecutorCreator<FloatingPointAttributeTemplate<double>> creator; if (creator.handle(attribute)) return creator.create(stash, idx); } + { MultiValueExecutorCreator<FloatingPointAttributeTemplate<float>> creator; if (creator.handle(attribute)) return creator.create(stash, idx); } return stash.create<AttributeExecutor<FloatContent>>(attribute, idx); } } @@ -415,6 +419,63 @@ createTensorAttributeExecutor(const IAttributeVector *attribute, const vespalib: } +AttributeBlueprint::AttributeBlueprint() : + fef::Blueprint("attribute"), + _attrName(), + _attrKey(), + _extra(), + _tensorType(ValueType::double_type()) +{ +} + +AttributeBlueprint::~AttributeBlueprint() = default; + +void +AttributeBlueprint::visitDumpFeatures(const fef::IIndexEnvironment &, + fef::IDumpFeatureVisitor &) const +{ +} + +bool +AttributeBlueprint::setup(const fef::IIndexEnvironment & env, + const fef::ParameterList & params) +{ + // params[0] = attribute name + // params[1] = index (array attribute) or key (weighted set attribute) + _attrName = params[0].getValue(); + _attrKey = createAttributeKey(_attrName); + if (params.size() == 2) { + _extra = params[1].getValue(); + } + vespalib::string attrType = type::Attribute::lookup(env.getProperties(), _attrName); + if (!attrType.empty()) { + _tensorType = ValueType::from_spec(attrType); + if (_tensorType.is_error()) { + LOG(error, "%s: invalid type: '%s'", getName().c_str(), attrType.c_str()); + } + } + FeatureType output_type = _tensorType.is_double() + ? FeatureType::number() + : FeatureType::object(_tensorType); + describeOutput("value", "The value of a single value attribute, " + "the value at the given index of an array attribute, " + "the given key of a weighted set attribute, or" + "the tensor of a tensor attribute", output_type); + if (!_tensorType.is_tensor()) { + describeOutput("weight", "The weight associated with the given key in a weighted set attribute."); + describeOutput("contains", "1 if the given key is present in a weighted set attribute, 0 otherwise."); + describeOutput("count", "Returns the number of elements in this array or weighted set attribute."); + } + env.hintAttributeAccess(_attrName); + return !_tensorType.is_error(); +} + +fef::Blueprint::UP +AttributeBlueprint::createInstance() const +{ + return std::make_unique<AttributeBlueprint>(); +} + void AttributeBlueprint::prepareSharedState(const fef::IQueryEnvironment & env, fef::IObjectStore & store) const { diff --git a/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp b/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp index e3f4cee4836..8998f01b59e 100644 --- a/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp +++ b/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp @@ -444,6 +444,9 @@ ArrayParam<T>::~ArrayParam() = default; // FIXME this feels a bit dirty, consider breaking up ArrayParam to remove dependencies // on templated vector parsing. This is why it's defined in this translation unit as it is. template ArrayParam<int64_t>::ArrayParam(const Property & prop); +#ifdef __clang__ +template ArrayParam<int64_t>::~ArrayParam(); +#endif template struct ArrayParam<double>; template struct ArrayParam<float>; diff --git a/searchlib/src/vespa/searchlib/fef/featureexecutor.h b/searchlib/src/vespa/searchlib/fef/featureexecutor.h index dc8a4ba6075..1e52aaee8ad 100644 --- a/searchlib/src/vespa/searchlib/fef/featureexecutor.h +++ b/searchlib/src/vespa/searchlib/fef/featureexecutor.h @@ -58,10 +58,10 @@ public: }; class Outputs { - vespalib::ArrayRef<NumberOrObject> _outputs; public: + using OutputArray = vespalib::ArrayRef<NumberOrObject>; Outputs() : _outputs() {} - void bind(vespalib::ArrayRef<NumberOrObject> outputs) { _outputs = outputs; } + void bind(OutputArray outputs) { _outputs = outputs; } void set_number(size_t idx, feature_t value) { _outputs[idx].as_number = value; } @@ -83,7 +83,12 @@ public: const NumberOrObject *get_raw(size_t idx) const { return &_outputs[idx]; } + OutputArray get_bound() const { + return _outputs; + } size_t size() const { return _outputs.size(); } + private: + vespalib::ArrayRef<NumberOrObject> _outputs; }; private: diff --git a/searchlib/src/vespa/searchlib/predicate/predicate_index.h b/searchlib/src/vespa/searchlib/predicate/predicate_index.h index 196c1df16de..b0fb0eda4c5 100644 --- a/searchlib/src/vespa/searchlib/predicate/predicate_index.h +++ b/searchlib/src/vespa/searchlib/predicate/predicate_index.h @@ -29,7 +29,7 @@ class PredicateIndex : public PopulateInterface { using FeatureMap = std::unordered_map<uint64_t, std::vector<IntervalT>>; using generation_t = vespalib::GenerationHandler::generation_t; template <typename T> - using optional = std::experimental::optional<T>; + using optional = std::optional<T>; public: typedef std::unique_ptr<PredicateIndex> UP; diff --git a/searchlib/src/vespa/searchlib/predicate/simple_index.h b/searchlib/src/vespa/searchlib/predicate/simple_index.h index 4edc0ff2d14..986b46d7008 100644 --- a/searchlib/src/vespa/searchlib/predicate/simple_index.h +++ b/searchlib/src/vespa/searchlib/predicate/simple_index.h @@ -6,7 +6,7 @@ #include <vespa/vespalib/btree/btreestore.h> #include <vespa/vespalib/data/databuffer.h> #include <vespa/vespalib/util/rcuvector.h> -#include <experimental/optional> +#include <optional> namespace search::predicate { @@ -139,7 +139,7 @@ private: using GenerationHolder = vespalib::GenerationHolder; using generation_t = vespalib::GenerationHandler::generation_t; template <typename T> - using optional = std::experimental::optional<T>; + using optional = std::optional<T>; Dictionary _dictionary; BTreeStore _btree_posting_lists; diff --git a/searchlib/src/vespa/searchlib/queryeval/predicate_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/predicate_blueprint.h index 3d92b19c421..16b725cd4b0 100644 --- a/searchlib/src/vespa/searchlib/queryeval/predicate_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/predicate_blueprint.h @@ -56,7 +56,7 @@ private: using BTreeIterator = predicate::SimpleIndex<datastore::EntryRef>::BTreeIterator; using VectorIterator = predicate::SimpleIndex<datastore::EntryRef>::VectorIterator; template <typename T> - using optional = std::experimental::optional<T>; + using optional = std::optional<T>; using Alloc = vespalib::alloc::Alloc; const PredicateAttribute & predicate_attribute() const { diff --git a/searchlib/src/vespa/searchlib/transactionlog/translogserverapp.cpp b/searchlib/src/vespa/searchlib/transactionlog/translogserverapp.cpp index 0b02d10ffab..b8d21fb7465 100644 --- a/searchlib/src/vespa/searchlib/transactionlog/translogserverapp.cpp +++ b/searchlib/src/vespa/searchlib/transactionlog/translogserverapp.cpp @@ -28,9 +28,9 @@ DomainPart::Crc getCrc(searchlib::TranslogserverConfig::Crcmethod crcType) { switch (crcType) { - case searchlib::TranslogserverConfig::ccitt_crc32: + case searchlib::TranslogserverConfig::Crcmethod::ccitt_crc32: return DomainPart::ccitt_crc32; - case searchlib::TranslogserverConfig::xxh64: + case searchlib::TranslogserverConfig::Crcmethod::xxh64: return DomainPart::xxh64; } LOG_ABORT("should not be reached"); diff --git a/security-tools/src/main/sh/vespa-curl-wrapper b/security-tools/src/main/sh/vespa-curl-wrapper index da857984c01..da1465a07bc 100755 --- a/security-tools/src/main/sh/vespa-curl-wrapper +++ b/security-tools/src/main/sh/vespa-curl-wrapper @@ -6,7 +6,7 @@ set -e -eval $(vespa-security-env) +eval $($VESPA_HOME/bin/vespa-security-env) CURL_PARAMETERS=("$@") diff --git a/staging_vespalib/src/tests/xmlserializable/xmlserializabletest.cpp b/staging_vespalib/src/tests/xmlserializable/xmlserializabletest.cpp index f2990d7b511..a389ada2214 100644 --- a/staging_vespalib/src/tests/xmlserializable/xmlserializabletest.cpp +++ b/staging_vespalib/src/tests/xmlserializable/xmlserializabletest.cpp @@ -56,7 +56,7 @@ Test::testEscaping() std::ostringstream ost; XmlOutputStream xos(ost); using namespace vespalib::xml; - xos << XmlTag("!#trash%-", CONVERT_ILLEGAL_CHARACTERS) + xos << XmlTag("!#trash%-", XmlTagFlags::CONVERT_ILLEGAL_CHARACTERS) << XmlTag("foo") << XmlAttribute("bar", "<100%\" &\n>") << XmlEndTag() diff --git a/staging_vespalib/src/vespa/vespalib/util/xmlstream.cpp b/staging_vespalib/src/vespa/vespalib/util/xmlstream.cpp index b5fd5b10844..e1778a881be 100644 --- a/staging_vespalib/src/vespa/vespalib/util/xmlstream.cpp +++ b/staging_vespalib/src/vespa/vespalib/util/xmlstream.cpp @@ -361,7 +361,7 @@ XmlTag::XmlTag(const std::string& name, XmlTagFlags flags) _content(), _flags(flags) { - if (_flags == CONVERT_ILLEGAL_CHARACTERS) { + if (_flags == XmlTagFlags::CONVERT_ILLEGAL_CHARACTERS) { convertToLegalName(_name); } if (!isLegalName(_name)) { diff --git a/staging_vespalib/src/vespa/vespalib/util/xmlstream.h b/staging_vespalib/src/vespa/vespalib/util/xmlstream.h index 5455251eea1..01f3104a595 100644 --- a/staging_vespalib/src/vespa/vespalib/util/xmlstream.h +++ b/staging_vespalib/src/vespa/vespalib/util/xmlstream.h @@ -42,7 +42,7 @@ class XmlOutputStream; bool isLegalName(const std::string& name); -enum XmlTagFlags { NONE = 0, CONVERT_ILLEGAL_CHARACTERS = 1 }; +enum class XmlTagFlags { NONE = 0, CONVERT_ILLEGAL_CHARACTERS = 1 }; /** * @class document::XmlTag @@ -56,7 +56,7 @@ class XmlTag { XmlTagFlags _flags; public: XmlTag(const XmlTag&); - XmlTag(const std::string& name, XmlTagFlags = NONE); + XmlTag(const std::string& name, XmlTagFlags = XmlTagFlags::NONE); ~XmlTag(); const std::string& getName() const { return _name; } diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt index 418d8dbe430..52423f031e5 100644 --- a/storage/CMakeLists.txt +++ b/storage/CMakeLists.txt @@ -47,7 +47,7 @@ vespa_define_module( TEST_DEPENDS messagebus_messagebus-test vdstestlib - atomic + ${VESPA_ATOMIC_LIB} TESTS src/tests diff --git a/storage/src/tests/distributor/distributortest.cpp b/storage/src/tests/distributor/distributortest.cpp index 2710ed67717..dfc67fd6f5a 100644 --- a/storage/src/tests/distributor/distributortest.cpp +++ b/storage/src/tests/distributor/distributortest.cpp @@ -607,15 +607,15 @@ TEST_F(DistributorTest, host_info_reporter_config_is_propagated_to_reporter) { TEST_F(DistributorTest, replica_counting_mode_is_configured_to_trusted_by_default) { setupDistributor(Redundancy(2), NodeCount(2), "storage:2 distributor:1"); - EXPECT_EQ(ConfigBuilder::TRUSTED, currentReplicaCountingMode()); + EXPECT_EQ(ConfigBuilder::MinimumReplicaCountingMode::TRUSTED, currentReplicaCountingMode()); } TEST_F(DistributorTest, replica_counting_mode_config_is_propagated_to_metric_updater) { setupDistributor(Redundancy(2), NodeCount(2), "storage:2 distributor:1"); ConfigBuilder builder; - builder.minimumReplicaCountingMode = ConfigBuilder::ANY; + builder.minimumReplicaCountingMode = ConfigBuilder::MinimumReplicaCountingMode::ANY; configureDistributor(builder); - EXPECT_EQ(ConfigBuilder::ANY, currentReplicaCountingMode()); + EXPECT_EQ(ConfigBuilder::MinimumReplicaCountingMode::ANY, currentReplicaCountingMode()); } TEST_F(DistributorTest, bucket_activation_is_enabled_by_default) { diff --git a/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp b/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp index c804354b0ee..05e51993f49 100644 --- a/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp +++ b/storage/src/tests/persistence/common/persistenceproviderwrapper.cpp @@ -14,7 +14,7 @@ #define CHECK_ERROR(className, failType) \ { \ - if (_result.getErrorCode() != spi::Result::NONE && (_failureMask & (failType))) { \ + if (_result.getErrorCode() != spi::Result::ErrorType::NONE && (_failureMask & (failType))) { \ return className(_result.getErrorCode(), _result.getErrorMessage()); \ } \ } @@ -41,7 +41,7 @@ includedVersionsToString(spi::IncludedVersions versions) PersistenceProviderWrapper::PersistenceProviderWrapper(spi::PersistenceProvider& spi) : _spi(spi), - _result(spi::Result(spi::Result::NONE, "")), + _result(spi::Result(spi::Result::ErrorType::NONE, "")), _log(), _failureMask(0) { } diff --git a/storage/src/tests/persistence/common/persistenceproviderwrapper.h b/storage/src/tests/persistence/common/persistenceproviderwrapper.h index 1f0dc93c44c..511ced02118 100644 --- a/storage/src/tests/persistence/common/persistenceproviderwrapper.h +++ b/storage/src/tests/persistence/common/persistenceproviderwrapper.h @@ -65,7 +65,7 @@ public: _result = result; } void clearResult() { - _result = spi::Result(spi::Result::NONE, ""); + _result = spi::Result(spi::Result::ErrorType::NONE, ""); } const spi::Result& getResult() const { return _result; } /** diff --git a/storage/src/tests/persistence/mergehandlertest.cpp b/storage/src/tests/persistence/mergehandlertest.cpp index 8dedf3f18df..0b2baab5652 100644 --- a/storage/src/tests/persistence/mergehandlertest.cpp +++ b/storage/src/tests/persistence/mergehandlertest.cpp @@ -631,7 +631,7 @@ TEST_F(MergeHandlerTest, spi_flush_guard) { MergeHandler handler(providerWrapper, getEnv()); providerWrapper.setResult( - spi::Result(spi::Result::PERMANENT_ERROR, "who you gonna call?")); + spi::Result(spi::Result::ErrorType::PERMANENT_ERROR, "who you gonna call?")); setUpChain(MIDDLE); // Fail applying unrevertable remove @@ -826,7 +826,7 @@ TEST_F(MergeHandlerTest, merge_bucket_spi_failures) { PersistenceProviderWrapper providerWrapper(getPersistenceProvider()); MergeHandler handler(providerWrapper, getEnv()); providerWrapper.setResult( - spi::Result(spi::Result::PERMANENT_ERROR, "who you gonna call?")); + spi::Result(spi::Result::ErrorType::PERMANENT_ERROR, "who you gonna call?")); setUpChain(MIDDLE); ExpectedExceptionSpec exceptions[] = { @@ -858,7 +858,7 @@ TEST_F(MergeHandlerTest, get_bucket_diff_spi_failures) { PersistenceProviderWrapper providerWrapper(getPersistenceProvider()); MergeHandler handler(providerWrapper, getEnv()); providerWrapper.setResult( - spi::Result(spi::Result::PERMANENT_ERROR, "who you gonna call?")); + spi::Result(spi::Result::ErrorType::PERMANENT_ERROR, "who you gonna call?")); setUpChain(MIDDLE); ExpectedExceptionSpec exceptions[] = { @@ -893,7 +893,7 @@ TEST_F(MergeHandlerTest, apply_bucket_diff_spi_failures) { PersistenceProviderWrapper providerWrapper(getPersistenceProvider()); MergeHandler handler(providerWrapper, getEnv()); providerWrapper.setResult( - spi::Result(spi::Result::PERMANENT_ERROR, "who you gonna call?")); + spi::Result(spi::Result::ErrorType::PERMANENT_ERROR, "who you gonna call?")); setUpChain(MIDDLE); ExpectedExceptionSpec exceptions[] = { @@ -960,7 +960,7 @@ TEST_F(MergeHandlerTest, get_bucket_diff_reply_spi_failures) { PersistenceProviderWrapper providerWrapper(getPersistenceProvider()); MergeHandler handler(providerWrapper, getEnv()); providerWrapper.setResult( - spi::Result(spi::Result::PERMANENT_ERROR, "who you gonna call?")); + spi::Result(spi::Result::ErrorType::PERMANENT_ERROR, "who you gonna call?")); HandleGetBucketDiffReplyInvoker invoker; setUpChain(FRONT); @@ -1051,7 +1051,7 @@ TEST_F(MergeHandlerTest, apply_bucket_diff_reply_spi_failures) { invoker.setChainPos(pos); MergeHandler handler(providerWrapper, getEnv()); providerWrapper.setResult( - spi::Result(spi::Result::PERMANENT_ERROR, "who you gonna call?")); + spi::Result(spi::Result::ErrorType::PERMANENT_ERROR, "who you gonna call?")); ExpectedExceptionSpec exceptions[] = { { PersistenceProviderWrapper::FAIL_CREATE_ITERATOR, "create iterator" }, diff --git a/storage/src/tests/persistence/provider_error_wrapper_test.cpp b/storage/src/tests/persistence/provider_error_wrapper_test.cpp index 36238abb238..2ba5218003b 100644 --- a/storage/src/tests/persistence/provider_error_wrapper_test.cpp +++ b/storage/src/tests/persistence/provider_error_wrapper_test.cpp @@ -64,7 +64,7 @@ TEST_F(ProviderErrorWrapperTest, fatal_error_invokes_listener) { Fixture f(getPersistenceProvider()); auto listener = std::make_shared<MockErrorListener>(); f.errorWrapper.register_error_listener(listener); - f.providerWrapper.setResult(spi::Result(spi::Result::FATAL_ERROR, "eject! eject!")); + f.providerWrapper.setResult(spi::Result(spi::Result::ErrorType::FATAL_ERROR, "eject! eject!")); EXPECT_FALSE(listener->_seen_fatal_error); f.perform_spi_operation(); @@ -78,7 +78,7 @@ TEST_F(ProviderErrorWrapperTest, resource_exhaustion_error_invokes_listener) { Fixture f(getPersistenceProvider()); auto listener = std::make_shared<MockErrorListener>(); f.errorWrapper.register_error_listener(listener); - f.providerWrapper.setResult(spi::Result(spi::Result::RESOURCE_EXHAUSTED, "out of juice")); + f.providerWrapper.setResult(spi::Result(spi::Result::ErrorType::RESOURCE_EXHAUSTED, "out of juice")); EXPECT_FALSE(listener->_seen_resource_exhaustion_error); f.perform_spi_operation(); @@ -103,8 +103,8 @@ TEST_F(ProviderErrorWrapperTest, listener_not_invoked_on_regular_errors) { auto listener = std::make_shared<MockErrorListener>(); f.errorWrapper.register_error_listener(listener); - EXPECT_NO_FATAL_FAILURE(f.check_no_listener_invoked_for_error(*listener, spi::Result::TRANSIENT_ERROR)); - EXPECT_NO_FATAL_FAILURE(f.check_no_listener_invoked_for_error(*listener, spi::Result::PERMANENT_ERROR)); + EXPECT_NO_FATAL_FAILURE(f.check_no_listener_invoked_for_error(*listener, spi::Result::ErrorType::TRANSIENT_ERROR)); + EXPECT_NO_FATAL_FAILURE(f.check_no_listener_invoked_for_error(*listener, spi::Result::ErrorType::PERMANENT_ERROR)); } TEST_F(ProviderErrorWrapperTest, multiple_listeners_can_be_registered) { @@ -114,7 +114,7 @@ TEST_F(ProviderErrorWrapperTest, multiple_listeners_can_be_registered) { f.errorWrapper.register_error_listener(listener1); f.errorWrapper.register_error_listener(listener2); - f.providerWrapper.setResult(spi::Result(spi::Result::RESOURCE_EXHAUSTED, "out of juice")); + f.providerWrapper.setResult(spi::Result(spi::Result::ErrorType::RESOURCE_EXHAUSTED, "out of juice")); f.perform_spi_operation(); EXPECT_TRUE(listener1->_seen_resource_exhaustion_error); diff --git a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h index 8d31a228543..08b545057cf 100644 --- a/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h +++ b/storage/src/vespa/storage/distributor/operations/idealstate/idealstateoperation.h @@ -218,7 +218,7 @@ public: virtual bool shouldBlockThisOperation(uint32_t messageType, uint8_t priority) const; protected: - friend class IdealStateManagerTest; + friend struct IdealStateManagerTest; friend class IdealStateManager; IdealStateManager* _manager; diff --git a/storage/src/vespa/storage/persistence/bucketprocessor.cpp b/storage/src/vespa/storage/persistence/bucketprocessor.cpp index c512cdd461a..d4a570ee062 100644 --- a/storage/src/vespa/storage/persistence/bucketprocessor.cpp +++ b/storage/src/vespa/storage/persistence/bucketprocessor.cpp @@ -52,7 +52,7 @@ BucketProcessor::iterateAll(spi::PersistenceProvider& provider, versions, context)); - if (createIterResult.getErrorCode() != spi::Result::NONE) { + if (createIterResult.getErrorCode() != spi::Result::ErrorType::NONE) { vespalib::asciistream ss; ss << "Failed to create iterator: " << createIterResult.getErrorMessage(); @@ -65,7 +65,7 @@ BucketProcessor::iterateAll(spi::PersistenceProvider& provider, while (true) { spi::IterateResult result( provider.iterate(iteratorId, UINT64_MAX, context)); - if (result.getErrorCode() != spi::Result::NONE) { + if (result.getErrorCode() != spi::Result::ErrorType::NONE) { vespalib::asciistream ss; ss << "Failed: " << result.getErrorMessage(); throw std::runtime_error(ss.str()); diff --git a/storage/src/vespa/storage/persistence/mergehandler.cpp b/storage/src/vespa/storage/persistence/mergehandler.cpp index f5d7bc57354..37e1d818bb8 100644 --- a/storage/src/vespa/storage/persistence/mergehandler.cpp +++ b/storage/src/vespa/storage/persistence/mergehandler.cpp @@ -169,7 +169,7 @@ MergeHandler::populateMetaData( spi::ALL_VERSIONS, context)); - if (createIterResult.getErrorCode() != spi::Result::NONE) { + if (createIterResult.getErrorCode() != spi::Result::ErrorType::NONE) { std::ostringstream ss; ss << "Failed to create iterator for " << bucket @@ -183,7 +183,7 @@ MergeHandler::populateMetaData( while (true) { spi::IterateResult result( _spi.iterate(iteratorId, UINT64_MAX, context)); - if (result.getErrorCode() != spi::Result::NONE) { + if (result.getErrorCode() != spi::Result::ErrorType::NONE) { std::ostringstream ss; ss << "Failed to iterate for " << bucket @@ -226,7 +226,7 @@ MergeHandler::buildBucketInfoList( if (entry.exist()) { spi::BucketInfoResult infoResult(_spi.getBucketInfo(bucket)); - if (infoResult.getErrorCode() != spi::Result::NONE) { + if (infoResult.getErrorCode() != spi::Result::ErrorType::NONE) { std::ostringstream ss; ss << "Failed to get bucket info for " << bucket << ": " @@ -432,7 +432,7 @@ MergeHandler::fetchLocalData( spi::NEWEST_DOCUMENT_OR_REMOVE, context)); - if (createIterResult.getErrorCode() != spi::Result::NONE) { + if (createIterResult.getErrorCode() != spi::Result::ErrorType::NONE) { std::ostringstream ss; ss << "Failed to create iterator for " << bucket.toString() @@ -451,7 +451,7 @@ MergeHandler::fetchLocalData( while (true) { spi::IterateResult result( _spi.iterate(iteratorId, remainingSize, context)); - if (result.getErrorCode() != spi::Result::NONE) { + if (result.getErrorCode() != spi::Result::ErrorType::NONE) { std::ostringstream ss; ss << "Failed to iterate for " << bucket.toString() @@ -712,7 +712,7 @@ MergeHandler::applyDiffLocally( flushGuard.flush(); spi::BucketInfoResult infoResult(_spi.getBucketInfo(bucket)); - if (infoResult.getErrorCode() != spi::Result::NONE) { + if (infoResult.getErrorCode() != spi::Result::ErrorType::NONE) { LOG(warning, "Failed to get bucket info for %s: %s", bucket.toString().c_str(), infoResult.getErrorMessage().c_str()); diff --git a/storage/src/vespa/storage/persistence/persistenceutil.cpp b/storage/src/vespa/storage/persistence/persistenceutil.cpp index 4bee5df4d04..9cea0e6c5d7 100644 --- a/storage/src/vespa/storage/persistence/persistenceutil.cpp +++ b/storage/src/vespa/storage/persistence/persistenceutil.cpp @@ -182,16 +182,16 @@ uint32_t PersistenceUtil::convertErrorCode(const spi::Result& response) { switch (response.getErrorCode()) { - case spi::Result::NONE: + case spi::Result::ErrorType::NONE: return 0; - case spi::Result::TIMESTAMP_EXISTS: + case spi::Result::ErrorType::TIMESTAMP_EXISTS: return api::ReturnCode::TIMESTAMP_EXIST; - case spi::Result::TRANSIENT_ERROR: - case spi::Result::FATAL_ERROR: + case spi::Result::ErrorType::TRANSIENT_ERROR: + case spi::Result::ErrorType::FATAL_ERROR: return mbus::ErrorCode::APP_TRANSIENT_ERROR; - case spi::Result::RESOURCE_EXHAUSTED: + case spi::Result::ErrorType::RESOURCE_EXHAUSTED: return api::ReturnCode::NO_SPACE; - case spi::Result::PERMANENT_ERROR: + case spi::Result::ErrorType::PERMANENT_ERROR: default: return mbus::ErrorCode::APP_FATAL_ERROR; } diff --git a/storage/src/vespa/storage/persistence/processallhandler.cpp b/storage/src/vespa/storage/persistence/processallhandler.cpp index 51ef67dc7ac..7cb373279a6 100644 --- a/storage/src/vespa/storage/persistence/processallhandler.cpp +++ b/storage/src/vespa/storage/persistence/processallhandler.cpp @@ -39,7 +39,7 @@ public: *entry.getDocumentId(), _context); - if (removeResult.getErrorCode() != spi::Result::NONE) { + if (removeResult.getErrorCode() != spi::Result::ErrorType::NONE) { std::ostringstream ss; ss << "Failed to do remove for removelocation: " << removeResult.getErrorMessage(); diff --git a/storage/src/vespa/storage/persistence/provider_error_wrapper.cpp b/storage/src/vespa/storage/persistence/provider_error_wrapper.cpp index 056561e8e21..fe947bff4d5 100644 --- a/storage/src/vespa/storage/persistence/provider_error_wrapper.cpp +++ b/storage/src/vespa/storage/persistence/provider_error_wrapper.cpp @@ -9,9 +9,9 @@ template <typename ResultType> ResultType ProviderErrorWrapper::checkResult(ResultType&& result) const { - if (result.getErrorCode() == spi::Result::FATAL_ERROR) { + if (result.getErrorCode() == spi::Result::ErrorType::FATAL_ERROR) { trigger_shutdown_listeners(result.getErrorMessage()); - } else if (result.getErrorCode() == spi::Result::RESOURCE_EXHAUSTED) { + } else if (result.getErrorCode() == spi::Result::ErrorType::RESOURCE_EXHAUSTED) { trigger_resource_exhaustion_listeners(result.getErrorMessage()); } return std::forward<ResultType>(result); diff --git a/storageapi/src/vespa/storageapi/messageapi/maintenancecommand.h b/storageapi/src/vespa/storageapi/messageapi/maintenancecommand.h index 029365973c5..1b149780c9d 100644 --- a/storageapi/src/vespa/storageapi/messageapi/maintenancecommand.h +++ b/storageapi/src/vespa/storageapi/messageapi/maintenancecommand.h @@ -12,8 +12,10 @@ public: MaintenanceCommand(const MessageType& type, const document::Bucket &bucket) : BucketInfoCommand(type, bucket) {} + MaintenanceCommand(const MaintenanceCommand &) = default; MaintenanceCommand(MaintenanceCommand &&) = default; - MaintenanceCommand & operator = (MaintenanceCommand &&) = default; + MaintenanceCommand & operator = (const MaintenanceCommand &) = delete; + MaintenanceCommand & operator = (MaintenanceCommand &&) = delete; ~MaintenanceCommand(); const vespalib::string& getReason() const { return _reason; }; diff --git a/storageserver/src/apps/storaged/storage.cpp b/storageserver/src/apps/storaged/storage.cpp index 0261d8e587e..b1eafcbad93 100644 --- a/storageserver/src/apps/storaged/storage.cpp +++ b/storageserver/src/apps/storaged/storage.cpp @@ -37,8 +37,8 @@ Process::UP createProcess(vespalib::stringref configId) { if (serverConfig->isDistributor) { return Process::UP(new DistributorProcess(configId)); } else switch (serverConfig->persistenceProvider.type) { - case vespa::config::content::core::StorServerConfig::PersistenceProvider::STORAGE: - case vespa::config::content::core::StorServerConfig::PersistenceProvider::DUMMY: + case vespa::config::content::core::StorServerConfig::PersistenceProvider::Type::STORAGE: + case vespa::config::content::core::StorServerConfig::PersistenceProvider::Type::DUMMY: return Process::UP(new DummyServiceLayerProcess(configId)); default: throw vespalib::IllegalStateException("Unknown persistence provider.", VESPA_STRLOC); diff --git a/storageserver/src/vespa/storageserver/app/distributorprocess.cpp b/storageserver/src/vespa/storageserver/app/distributorprocess.cpp index 6ef391fa56a..57fdcdeb248 100644 --- a/storageserver/src/vespa/storageserver/app/distributorprocess.cpp +++ b/storageserver/src/vespa/storageserver/app/distributorprocess.cpp @@ -32,7 +32,7 @@ DistributorProcess::setupConfig(uint64_t subscribeTimeout) std::unique_ptr<vespa::config::content::core::StorServerConfig> config = config::ConfigGetter<vespa::config::content::core::StorServerConfig>::getConfig(_configUri.getConfigId(), _configUri.getContext(), subscribeTimeout); if (config->persistenceProvider.type - != vespa::config::content::core::StorServerConfig::PersistenceProvider::STORAGE) + != vespa::config::content::core::StorServerConfig::PersistenceProvider::Type::STORAGE) { _activeFlag = DistributorNode::NEED_ACTIVE_BUCKET_STATES_SET; } diff --git a/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp b/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp index 4ed4e9462d3..ba94caa8965 100644 --- a/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp +++ b/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp @@ -43,7 +43,7 @@ RankManager::Snapshot::detectFields(const VsmfieldsHandle & fields) { for (uint32_t i = 0; i < fields->fieldspec.size(); ++i) { const VsmfieldsConfig::Fieldspec & fs = fields->fieldspec[i]; - bool isAttribute = (fs.fieldtype == VsmfieldsConfig::Fieldspec::ATTRIBUTE); + bool isAttribute = (fs.fieldtype == VsmfieldsConfig::Fieldspec::Fieldtype::ATTRIBUTE); LOG(debug, "Adding field of type '%s' and name '%s' with id '%u' the index environment.", isAttribute ? "ATTRIBUTE" : "INDEX", fs.name.c_str(), i); // This id must match the vsm specific field id diff --git a/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java b/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java index e3caaeb455c..680021893f7 100644 --- a/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java +++ b/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java @@ -237,7 +237,9 @@ public class Group implements Comparable<Group> { } this.distributionSpec = distributionSpec; // Create the pre calculated results - if (maxRedundancy <= 0 || maxRedundancy > 255) throw new IllegalArgumentException("The max redundancy must be a positive number in the range 1-255."); + if (maxRedundancy <= 0 || maxRedundancy > 255) { + throw new IllegalArgumentException("The max redundancy (" + maxRedundancy + ") must be a positive number in the range 1-255."); + } int asterixCount = distributionSpec.length - firstAsterix; int[][] preCalculations = new int[maxRedundancy + 1][]; for (int i=1; i<=maxRedundancy; ++i) { diff --git a/vdslib/src/test/java/com/yahoo/vdslib/distribution/GroupTestCase.java b/vdslib/src/test/java/com/yahoo/vdslib/distribution/GroupTestCase.java index c71216b192d..e81d77d5972 100644 --- a/vdslib/src/test/java/com/yahoo/vdslib/distribution/GroupTestCase.java +++ b/vdslib/src/test/java/com/yahoo/vdslib/distribution/GroupTestCase.java @@ -48,7 +48,7 @@ public class GroupTestCase { assertDistribution("*|*|*", 5, "2,2,1"); assertDistribution("*|*|*|*", 5, "2,1,1,1"); - assertDistributionFailure("2|*", 0, "The max redundancy must be a positive number in the range 1-255."); + assertDistributionFailure("2|*", 0, "The max redundancy (0) must be a positive number in the range 1-255."); assertDistributionFailure("*|2", 3, "Illegal distribution spec \"*|2\". Asterix specification must be tailing the specification."); assertDistributionFailure("*|2|*", 3, "Illegal distribution spec \"*|2|*\". Asterix specification must be tailing the specification."); assertDistributionFailure("0|*", 3, "Illegal distribution spec \"0|*\". Copy counts must be in the range 1-255."); diff --git a/vespa-hadoop/pom.xml b/vespa-hadoop/pom.xml index 1ee29c36b7d..ff21404a2c7 100644 --- a/vespa-hadoop/pom.xml +++ b/vespa-hadoop/pom.xml @@ -165,7 +165,16 @@ <shadedPattern>shaded.vespa</shadedPattern> </relocation> </relocations> - + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> </configuration> </execution> </executions> diff --git a/vespa-http-client/pom.xml b/vespa-http-client/pom.xml index 18dd34ee7be..96aa6defbe6 100644 --- a/vespa-http-client/pom.xml +++ b/vespa-http-client/pom.xml @@ -65,6 +65,22 @@ <artifactId>airline</artifactId> <version>0.6</version> </dependency> + <dependency> + <!-- Needed for Vespa TLS configuration. Standard jar artifact --> + <groupId>com.yahoo.vespa</groupId> + <artifactId>http-utils</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <!-- Needed for Vespa TLS configuration. --> + <!-- This artifact is packaged as an OSGi bundle - make sure to manually include or exclude transitive dependencies as necessary --> + <!-- Note: includes BouncyCastle to compile scope transitively. --> + <groupId>com.yahoo.vespa</groupId> + <artifactId>security-utils</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> <!-- Test dependencies --> <dependency> @@ -140,6 +156,17 @@ </goals> <configuration> <outputFile>target/${project.artifactId}-jar-with-dependencies.jar</outputFile> + <filters> + <filter> + <!-- Don't include signature files in uber jar (most likely from bouncycastle). --> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.yahoo.vespa.http.client.runner.Runner</mainClass> diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/config/ConnectionParams.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/config/ConnectionParams.java index f503190864b..ec9471e68ed 100644 --- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/config/ConnectionParams.java +++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/config/ConnectionParams.java @@ -8,6 +8,7 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import java.time.Duration; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -43,6 +44,17 @@ public final class ConnectionParams { private int traceLevel = 0; private int traceEveryXOperation = 0; private boolean printTraceToStdErr = true; + private boolean useTlsConfigFromEnvironment = false; + private Duration connectionTimeToLive = Duration.ofSeconds(15); + + /** + * Use TLS configuration through the standard Vespa environment variables. + * Setting this to 'true' will override any other TLS/HTTPS related configuration. + */ + public Builder setUseTlsConfigFromEnvironment(boolean useTlsConfigFromEnvironment) { + this.useTlsConfigFromEnvironment = useTlsConfigFromEnvironment; + return this; + } /** * Sets the SSLContext for the connection to the gateway when SSL is enabled for Endpoint. @@ -217,6 +229,13 @@ public final class ConnectionParams { return this; } + /** + * Set the maximum time to live for persistent connections + */ + public Builder setConnectionTimeToLive(Duration connectionTimeToLive) { + this.connectionTimeToLive = connectionTimeToLive; + return this; + } public ConnectionParams build() { return new ConnectionParams( @@ -233,7 +252,9 @@ public final class ConnectionParams { dryRun, traceLevel, traceEveryXOperation, - printTraceToStdErr); + printTraceToStdErr, + useTlsConfigFromEnvironment, + connectionTimeToLive); } public int getNumPersistentConnectionsPerEndpoint() { @@ -273,6 +294,14 @@ public final class ConnectionParams { public HostnameVerifier getHostnameVerifier() { return hostnameVerifier; } + + public boolean useTlsConfigFromEnvironment() { + return useTlsConfigFromEnvironment; + } + + public Duration getConnectionTimeToLive() { + return connectionTimeToLive; + } } private final SSLContext sslContext; private final HostnameVerifier hostnameVerifier; @@ -288,6 +317,8 @@ public final class ConnectionParams { private final int traceLevel; private final int traceEveryXOperation; private final boolean printTraceToStdErr; + private final boolean useTlsConfigFromEnvironment; + private final Duration connectionTimeToLive; private ConnectionParams( SSLContext sslContext, @@ -303,9 +334,13 @@ public final class ConnectionParams { boolean dryRun, int traceLevel, int traceEveryXOperation, - boolean printTraceToStdErr) { + boolean printTraceToStdErr, + boolean useTlsConfigFromEnvironment, + Duration connectionTimeToLive) { this.sslContext = sslContext; this.hostnameVerifier = hostnameVerifier; + this.useTlsConfigFromEnvironment = useTlsConfigFromEnvironment; + this.connectionTimeToLive = connectionTimeToLive; this.headers.putAll(headers); this.headerProviders.putAll(headerProviders); this.numPersistentConnectionsPerEndpoint = numPersistentConnectionsPerEndpoint; @@ -378,6 +413,14 @@ public final class ConnectionParams { return printTraceToStdErr; } + public boolean useTlsConfigFromEnvironment() { + return useTlsConfigFromEnvironment; + } + + public Duration getConnectionTimeToLive() { + return connectionTimeToLive; + } + /** * A header provider that provides a header value. {@link #getHeaderValue()} is called each time a new HTTP request * is constructed by {@link com.yahoo.vespa.http.client.FeedClient}. diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/ApacheGatewayConnection.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/ApacheGatewayConnection.java index 5289a7a562a..00f52d6337f 100644 --- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/ApacheGatewayConnection.java +++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/ApacheGatewayConnection.java @@ -17,14 +17,8 @@ import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpPost; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import java.io.ByteArrayInputStream; @@ -400,25 +394,24 @@ class ApacheGatewayConnection implements GatewayConnection { } public HttpClient createClient() { - HttpClientBuilder clientBuilder = HttpClientBuilder.create(); - if (useSsl && connectionParams.getSslContext() != null) { - Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() - .register("https", new SSLConnectionSocketFactory( - connectionParams.getSslContext(), connectionParams.getHostnameVerifier())) - .register("http", PlainConnectionSocketFactory.INSTANCE) - .build(); - PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - clientBuilder.setConnectionManager(connMgr); - + HttpClientBuilder clientBuilder; + if (connectionParams.useTlsConfigFromEnvironment()) { + clientBuilder = VespaTlsAwareClientBuilder.createHttpClientBuilder(); + } else { + clientBuilder = HttpClientBuilder.create(); + if (useSsl && connectionParams.getSslContext() != null) { + clientBuilder.setSslcontext(connectionParams.getSslContext()); + clientBuilder.setSSLHostnameVerifier(connectionParams.getHostnameVerifier()); + } } - clientBuilder.setUserAgent(String.format("vespa-http-client (%s)", Vtag.currentVersion)); - clientBuilder.setDefaultHeaders(Collections.singletonList(new BasicHeader(Headers.CLIENT_VERSION, Vtag.currentVersion))); clientBuilder.setMaxConnPerRoute(1); clientBuilder.setMaxConnTotal(1); + clientBuilder.setConnectionTimeToLive(connectionParams.getConnectionTimeToLive().getSeconds(), TimeUnit.SECONDS); + clientBuilder.setUserAgent(String.format("vespa-http-client (%s)", Vtag.currentVersion)); + clientBuilder.setDefaultHeaders(Collections.singletonList(new BasicHeader(Headers.CLIENT_VERSION, Vtag.currentVersion))); clientBuilder.disableContentCompression(); // Try to disable the disabling to see if system tests become stable again. // clientBuilder.disableAutomaticRetries(); - clientBuilder.setConnectionTimeToLive(15, TimeUnit.SECONDS); { RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); requestConfigBuilder.setSocketTimeout(0); diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/VespaTlsAwareClientBuilder.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/VespaTlsAwareClientBuilder.java new file mode 100644 index 00000000000..be67e11963e --- /dev/null +++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/VespaTlsAwareClientBuilder.java @@ -0,0 +1,26 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.http.client.core.communication; + +import org.apache.http.impl.client.HttpClientBuilder; + +/** + * A static factory for VespaHttpClientBuilder. + * The main purpose of this class is to avoid references to classes not compiled with JDK8. + * + * @author bjorncs + */ +// TODO Remove use of reflection once vespa-http-client only targets JDK11 +// The VespaTlsAwareClientBuilder class refers to classes in security-utils / http-utils that targets JDK11+. +class VespaTlsAwareClientBuilder { + + private VespaTlsAwareClientBuilder() {} + + static HttpClientBuilder createHttpClientBuilder() { + try { + Class<?> builderClass = Class.forName("ai.vespa.util.http.VespaHttpClientBuilder"); + return (HttpClientBuilder) builderClass.getMethod("create").invoke(null); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/Vtag.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/Vtag.java index f21a86dfc1b..f44a30208e7 100644 --- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/Vtag.java +++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/Vtag.java @@ -3,5 +3,5 @@ package com.yahoo.vespa.http.client.core.communication; class Vtag { - static final String currentVersion = "7.0.0"; + static final String currentVersion = "7.1.0"; } diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/runner/CommandLineArguments.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/runner/CommandLineArguments.java index 4e2c8f1509e..8a2a1652b4a 100644 --- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/runner/CommandLineArguments.java +++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/runner/CommandLineArguments.java @@ -20,6 +20,7 @@ import org.apache.http.message.BasicLineParser; import javax.inject.Inject; import java.net.MalformedURLException; import java.net.URL; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -110,6 +111,8 @@ public class CommandLineArguments { } } + // TODO Don't duplicate default values from ConnectionParams.Builder. Some defaults are already inconsistent. + @Inject private HelpOption helpOption; @@ -209,6 +212,14 @@ public class CommandLineArguments { description = "Add http header to every request. Header must have the format '<Name>: <Value>'. Use this parameter multiple times for multiple headers") private List<String> headers = new ArrayList<>(); + @Option(name = {"--vespaTls"}, + description = "BETA! Use Vespa TLS configuration from environment if available. Other HTTPS/TLS configuration will be ignored if this is set.") + private boolean useTlsConfigFromEnvironment = false; + + @Option(name = {"--connectionTimeToLive"}, + description = "Maximum time to live for persistent connections. Specified as integer, in seconds.") + private long connectionTimeToLive = 15; + private final List<Header> parsedHeaders = new ArrayList<>(); int getWhenVerboseEnabledPrintMessageForEveryXDocuments() { @@ -252,6 +263,8 @@ public class CommandLineArguments { .setTraceEveryXOperation(traceEveryXOperation) .setPrintTraceToStdErr(traceArg > 0) .setNumPersistentConnectionsPerEndpoint(numPersistentConnectionsPerEndpoint) + .setUseTlsConfigFromEnvironment(useTlsConfigFromEnvironment) + .setConnectionTimeToLive(Duration.ofSeconds(connectionTimeToLive)) .build() ) // Enable dynamic throttling. diff --git a/vespabase/src/vespa-configserver.service.in b/vespabase/src/vespa-configserver.service.in index b5bf28c1d73..7ee170bbdca 100644 --- a/vespabase/src/vespa-configserver.service.in +++ b/vespabase/src/vespa-configserver.service.in @@ -1,6 +1,7 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. [Unit] Description=Vertical Search Platform Config Server +After=network.target [Service] Type=forking diff --git a/vespabase/src/vespa.service.in b/vespabase/src/vespa.service.in index dec88ecb7cc..3c32f2c2cab 100644 --- a/vespabase/src/vespa.service.in +++ b/vespabase/src/vespa.service.in @@ -1,6 +1,7 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. [Unit] Description=Vertical Search Platform +After=network.target [Service] Type=forking diff --git a/vespaclient-container-plugin/pom.xml b/vespaclient-container-plugin/pom.xml index 959fb687692..53e708601d7 100644 --- a/vespaclient-container-plugin/pom.xml +++ b/vespaclient-container-plugin/pom.xml @@ -55,6 +55,10 @@ <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </exclusion> + <exclusion> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpcore</artifactId> + </exclusion> </exclusions> </dependency> <dependency> diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandler.java index a6fdcb10a00..848fe4b5726 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandler.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandler.java @@ -101,6 +101,10 @@ public interface OperationHandler { default Optional<String> get(RestUri restUri, Optional<String> fieldSet) throws RestApiException { return get(restUri); } + + default Optional<String> get(RestUri restUri, Optional<String> fieldSet, Optional<String> cluster) throws RestApiException { + return get(restUri, fieldSet); + } /** Called just before this is disposed of */ default void shutdown() {} diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java index 4ca43f5fde2..333f1d2f8c9 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java @@ -279,9 +279,13 @@ public class OperationHandlerImpl implements OperationHandler { } @Override - public Optional<String> get(RestUri restUri, Optional<String> fieldSet) throws RestApiException { + public Optional<String> get(RestUri restUri, Optional<String> fieldSet, Optional<String> cluster) throws RestApiException { SyncSession syncSession = syncSessions.alloc(); - setRoute(syncSession, Optional.empty()); + // Explicit unary used instead of map() due to unhandled exceptions, blargh. + Optional<String> route = cluster.isPresent() + ? Optional.of(clusterDefToRoute(resolveClusterDef(cluster, clusterEnumerator.enumerateClusters()))) + : Optional.empty(); + setRoute(syncSession, route); try { DocumentId id = new DocumentId(restUri.generateFullId()); final Document document = syncSession.get(id, fieldSet.orElse(restUri.getDocumentType() + ":[document]"), DocumentProtocol.Priority.NORMAL_1); @@ -301,6 +305,11 @@ public class OperationHandlerImpl implements OperationHandler { } @Override + public Optional<String> get(RestUri restUri, Optional<String> fieldSet) throws RestApiException { + return get(restUri, fieldSet, Optional.empty()); + } + + @Override public Optional<String> get(RestUri restUri) throws RestApiException { return get(restUri, Optional.empty()); } diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java index d2fd8d92495..7e572dae941 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java @@ -262,8 +262,9 @@ public class RestApi extends LoggingRequestHandler { } private HttpResponse handleGet(RestUri restUri, HttpRequest request) throws RestApiException { - final Optional<String> fieldSet = requestProperty("fieldSet", request); - final Optional<String> getDocument = operationHandler.get(restUri, fieldSet); + final Optional<String> fieldSet = requestProperty(FIELD_SET, request); + final Optional<String> cluster = requestProperty(CLUSTER, request); + final Optional<String> getDocument = operationHandler.get(restUri, fieldSet, cluster); final ObjectNode resultNode = mapper.createObjectNode(); if (getDocument.isPresent()) { final JsonNode parseNode; diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java index 374c91c13d3..e98e6babf84 100644 --- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java +++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java @@ -107,7 +107,7 @@ public class OperationHandlerImplTest { VisitorControlHandler.CompletionCode completionCode = VisitorControlHandler.CompletionCode.SUCCESS; int bucketsVisited = 0; Map<String, String> bucketSpaces = new HashMap<>(); - SyncSession mockSyncSession = mock(MessageBusSyncSession.class); // MBus session needed to avoid setRoute throwing. + MessageBusSyncSession mockSyncSession = mock(MessageBusSyncSession.class); // MBus session needed to avoid setRoute throwing. OperationHandlerImplFixture() { bucketSpaces.put("foo", "global"); @@ -317,6 +317,25 @@ public class OperationHandlerImplTest { } @Test + public void get_route_has_default_value_if_no_cluster_is_provided() throws Exception { + OperationHandlerImplFixture fixture = new OperationHandlerImplFixture(); + OperationHandlerImpl handler = fixture.createHandler(); + handler.get(dummyGetUri(), Optional.empty(), Optional.empty()); + + // TODO shouldn't this be default-get? + verify(fixture.mockSyncSession).setRoute(eq("default")); + } + + @Test + public void provided_get_cluster_is_propagated_as_route_to_sync_session() throws Exception { + OperationHandlerImplFixture fixture = new OperationHandlerImplFixture(); + OperationHandlerImpl handler = fixture.createHandler(); + handler.get(dummyGetUri(), Optional.empty(), Optional.of("foo")); + + verify(fixture.mockSyncSession).setRoute(eq("[Storage:cluster=foo;clusterconfigid=configId]")); + } + + @Test public void api_root_visit_uri_requires_cluster_set() throws Exception { OperationHandlerImplFixture fixture = new OperationHandlerImplFixture(); OperationHandlerImpl handler = fixture.createHandler(); diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java index 895c34436ce..0b3ee6d0792 100644 --- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java +++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java @@ -58,11 +58,20 @@ public class MockedOperationHandler implements OperationHandler { } @Override - public Optional<String> get(RestUri restUri, Optional<String> fieldSet) throws RestApiException { + public Optional<String> get(RestUri restUri, Optional<String> fieldSet, Optional<String> cluster) throws RestApiException { log.append("GET: " + restUri.generateFullId()); // This is _not_ an elegant way to return data back to the test. // An alternative is removing this entire class in favor of explicit mock expectations. - return fieldSet.map(fs -> String.format("{\"fields\": {\"fieldset\": \"%s\"}}", fs)); + if (!fieldSet.isPresent() && !cluster.isPresent()) { + return Optional.empty(); + } + return Optional.of(String.format("{\"fields\": {\"fieldset\": \"%s\",\"cluster\":\"%s\"}}", + fieldSet.orElse(""), cluster.orElse(""))); + } + + @Override + public Optional<String> get(RestUri restUri, Optional<String> fieldSet) throws RestApiException { + return get(restUri, fieldSet, Optional.empty()); } @Override diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java index 773d45b6b18..aeddc762586 100644 --- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java +++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java @@ -288,12 +288,19 @@ public class RestApiTest { } @Test - public void get_fieldset_parameter_is_propagated() throws IOException { + public void get_fieldset_parameter_is_propagated() { Request request = new Request(String.format("http://localhost:%s/document/v1/namespace/document-type/docid/bar?fieldSet=foo,baz", getFirstListenPort())); HttpGet get = new HttpGet(request.getUri()); assertHttp200ResponseContains(doRest(get), "\"fieldset\":\"foo,baz\""); } + @Test + public void get_cluster_parameter_is_propagated() { + Request request = new Request(String.format("http://localhost:%s/document/v1/namespace/document-type/docid/bar?cluster=my_cool_cluster", getFirstListenPort())); + HttpGet get = new HttpGet(request.getUri()); + assertHttp200ResponseContains(doRest(get), "\"cluster\":\"my_cool_cluster\""); + } + String visit_test_uri = "/document/v1/namespace/document-type/docid/?continuation=abc"; String visit_response_part1 = "\"documents\":[List of json docs, cont token abc, doc selection: '']"; String visit_response_part2 = "\"continuation\":\"token\""; diff --git a/vespaclient/CMakeLists.txt b/vespaclient/CMakeLists.txt index 8be8751f2c9..b0f4b2190f9 100644 --- a/vespaclient/CMakeLists.txt +++ b/vespaclient/CMakeLists.txt @@ -19,9 +19,13 @@ vespa_define_module( src/vespa/vespaclient/vesparoute ) -vespa_install_script(src/perl/bin/SetNodeState.pl vespa-set-node-state bin) -vespa_install_script(src/perl/bin/GetNodeState.pl vespa-get-node-state bin) -vespa_install_script(src/perl/bin/GetClusterState.pl vespa-get-cluster-state bin) +vespa_install_script(src/sh/vespa-set-node-state vespa-set-node-state bin) +vespa_install_script(src/sh/vespa-get-node-state vespa-get-node-state bin) +vespa_install_script(src/sh/vespa-get-cluster-state vespa-get-cluster-state bin) + +vespa_install_script(src/perl/bin/SetNodeState.pl libexec/vespa) +vespa_install_script(src/perl/bin/GetNodeState.pl libexec/vespa) +vespa_install_script(src/perl/bin/GetClusterState.pl libexec/vespa) install(DIRECTORY src/perl/lib/Yahoo/Vespa/ DESTINATION lib/perl5/site_perl/Yahoo/Vespa diff --git a/vespaclient/src/perl/lib/Yahoo/Vespa/Http.pm b/vespaclient/src/perl/lib/Yahoo/Vespa/Http.pm index 3a32aa64d22..f48d25906b4 100644 --- a/vespaclient/src/perl/lib/Yahoo/Vespa/Http.pm +++ b/vespaclient/src/perl/lib/Yahoo/Vespa/Http.pm @@ -97,6 +97,21 @@ sub setHttpExecutor { # (Function) sub initialize { # () %LEGAL_TYPES = map { $_ => 1 } ( 'GET', 'POST', 'PUT', 'DELETE'); $BROWSER = LWP::UserAgent->new; + my $tls_enabled = $ENV{'VESPA_TLS_ENABLED'}; + if (defined $tls_enabled and $tls_enabled eq '1') { + $BROWSER->ssl_opts( SSL_version => 'TLSv12'); + $BROWSER->ssl_opts( SSL_verifycn_scheme => 'none'); + $BROWSER->ssl_opts( SSL_cipher_list => 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-CHACHA20-POLY1305:TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256' ); + } + if (defined $ENV{'VESPA_TLS_CA_CERT'}) { + $BROWSER->ssl_opts( SSL_ca_file => $ENV{'VESPA_TLS_CA_CERT'} ); + } + if (defined $ENV{'VESPA_TLS_CERT'}) { + $BROWSER->ssl_opts( SSL_cert_file => $ENV{'VESPA_TLS_CERT'} ); + } + if (defined $ENV{'VESPA_TLS_PRIVATE_KEY'}) { + $BROWSER->ssl_opts( SSL_key_file => $ENV{'VESPA_TLS_PRIVATE_KEY'} ); + } $BROWSER->agent('Vespa-perl-script'); $EXECUTE = \&execute; } @@ -146,7 +161,8 @@ sub execute { # (Type, Host, Port, Path, Params, Content, Headers) -> Response } sub buildUri { # (Host, Port, Path) -> UriString my ($host, $port, $path) = @_; - my $uri = "http:"; + my $tls_enabled = $ENV{'VESPA_TLS_ENABLED'}; + my $uri = (defined $tls_enabled and $tls_enabled eq '1') ? "https:" : "http:"; if (defined $host) { $uri .= '//' . $host; if (defined $port) { diff --git a/vespaclient/src/sh/vespa-get-cluster-state b/vespaclient/src/sh/vespa-get-cluster-state new file mode 100755 index 00000000000..d1b0482cf3a --- /dev/null +++ b/vespaclient/src/sh/vespa-get-cluster-state @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# BEGIN environment bootstrap section +# Do not edit between here and END as this section should stay identical in all scripts + +findpath () { + myname=${0} + mypath=${myname%/*} + myname=${myname##*/} + if [ "$mypath" ] && [ -d "$mypath" ]; then + return + fi + mypath=$(pwd) + if [ -f "${mypath}/${myname}" ]; then + return + fi + echo "FATAL: Could not figure out the path where $myname lives from $0" + exit 1 +} + +COMMON_ENV=libexec/vespa/common-env.sh + +source_common_env () { + if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then + export VESPA_HOME + common_env=$VESPA_HOME/$COMMON_ENV + if [ -f "$common_env" ]; then + . $common_env + return + fi + fi + return 1 +} + +findroot () { + source_common_env && return + if [ "$VESPA_HOME" ]; then + echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" + exit 1 + fi + if [ "$ROOT" ] && [ -d "$ROOT" ]; then + VESPA_HOME="$ROOT" + source_common_env && return + fi + findpath + while [ "$mypath" ]; do + VESPA_HOME=${mypath} + source_common_env && return + mypath=${mypath%/*} + done + echo "FATAL: missing VESPA_HOME environment variable" + echo "Could not locate $COMMON_ENV anywhere" + exit 1 +} + +findhost () { + if [ "${VESPA_HOSTNAME}" = "" ]; then + VESPA_HOSTNAME=$(vespa-detect-hostname || hostname -f || hostname || echo "localhost") || exit 1 + fi + validate="${VESPA_HOME}/bin/vespa-validate-hostname" + if [ -f "$validate" ]; then + "$validate" "${VESPA_HOSTNAME}" || exit 1 + fi + export VESPA_HOSTNAME +} + +findroot +findhost + +# END environment bootstrap section + +eval $($VESPA_HOME/bin/vespa-security-env) + +exec $VESPA_HOME/libexec/vespa/GetClusterState.pl "$@" diff --git a/vespaclient/src/sh/vespa-get-node-state b/vespaclient/src/sh/vespa-get-node-state new file mode 100755 index 00000000000..d6d5a6905cf --- /dev/null +++ b/vespaclient/src/sh/vespa-get-node-state @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# BEGIN environment bootstrap section +# Do not edit between here and END as this section should stay identical in all scripts + +findpath () { + myname=${0} + mypath=${myname%/*} + myname=${myname##*/} + if [ "$mypath" ] && [ -d "$mypath" ]; then + return + fi + mypath=$(pwd) + if [ -f "${mypath}/${myname}" ]; then + return + fi + echo "FATAL: Could not figure out the path where $myname lives from $0" + exit 1 +} + +COMMON_ENV=libexec/vespa/common-env.sh + +source_common_env () { + if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then + export VESPA_HOME + common_env=$VESPA_HOME/$COMMON_ENV + if [ -f "$common_env" ]; then + . $common_env + return + fi + fi + return 1 +} + +findroot () { + source_common_env && return + if [ "$VESPA_HOME" ]; then + echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" + exit 1 + fi + if [ "$ROOT" ] && [ -d "$ROOT" ]; then + VESPA_HOME="$ROOT" + source_common_env && return + fi + findpath + while [ "$mypath" ]; do + VESPA_HOME=${mypath} + source_common_env && return + mypath=${mypath%/*} + done + echo "FATAL: missing VESPA_HOME environment variable" + echo "Could not locate $COMMON_ENV anywhere" + exit 1 +} + +findhost () { + if [ "${VESPA_HOSTNAME}" = "" ]; then + VESPA_HOSTNAME=$(vespa-detect-hostname || hostname -f || hostname || echo "localhost") || exit 1 + fi + validate="${VESPA_HOME}/bin/vespa-validate-hostname" + if [ -f "$validate" ]; then + "$validate" "${VESPA_HOSTNAME}" || exit 1 + fi + export VESPA_HOSTNAME +} + +findroot +findhost + +# END environment bootstrap section + +eval $($VESPA_HOME/bin/vespa-security-env) + +exec $VESPA_HOME/libexec/vespa/GetNodeState.pl "$@" diff --git a/vespaclient/src/sh/vespa-set-node-state b/vespaclient/src/sh/vespa-set-node-state new file mode 100755 index 00000000000..292ad8aef27 --- /dev/null +++ b/vespaclient/src/sh/vespa-set-node-state @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +# BEGIN environment bootstrap section +# Do not edit between here and END as this section should stay identical in all scripts + +findpath () { + myname=${0} + mypath=${myname%/*} + myname=${myname##*/} + if [ "$mypath" ] && [ -d "$mypath" ]; then + return + fi + mypath=$(pwd) + if [ -f "${mypath}/${myname}" ]; then + return + fi + echo "FATAL: Could not figure out the path where $myname lives from $0" + exit 1 +} + +COMMON_ENV=libexec/vespa/common-env.sh + +source_common_env () { + if [ "$VESPA_HOME" ] && [ -d "$VESPA_HOME" ]; then + export VESPA_HOME + common_env=$VESPA_HOME/$COMMON_ENV + if [ -f "$common_env" ]; then + . $common_env + return + fi + fi + return 1 +} + +findroot () { + source_common_env && return + if [ "$VESPA_HOME" ]; then + echo "FATAL: bad VESPA_HOME value '$VESPA_HOME'" + exit 1 + fi + if [ "$ROOT" ] && [ -d "$ROOT" ]; then + VESPA_HOME="$ROOT" + source_common_env && return + fi + findpath + while [ "$mypath" ]; do + VESPA_HOME=${mypath} + source_common_env && return + mypath=${mypath%/*} + done + echo "FATAL: missing VESPA_HOME environment variable" + echo "Could not locate $COMMON_ENV anywhere" + exit 1 +} + +findhost () { + if [ "${VESPA_HOSTNAME}" = "" ]; then + VESPA_HOSTNAME=$(vespa-detect-hostname || hostname -f || hostname || echo "localhost") || exit 1 + fi + validate="${VESPA_HOME}/bin/vespa-validate-hostname" + if [ -f "$validate" ]; then + "$validate" "${VESPA_HOSTNAME}" || exit 1 + fi + export VESPA_HOSTNAME +} + +findroot +findhost + +# END environment bootstrap section + +eval $($VESPA_HOME/bin/vespa-security-env) + +exec $VESPA_HOME/libexec/vespa/SetNodeState.pl "$@" diff --git a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp index 8ee10deead1..78838ce2cd2 100644 --- a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp +++ b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp @@ -97,7 +97,7 @@ struct Fixture { static std::unique_ptr<CryptoCodec> create_openssl_codec( const std::shared_ptr<TlsContext>& ctx, CryptoCodec::Mode mode) { auto ctx_impl = std::dynamic_pointer_cast<impl::OpenSslTlsContextImpl>(ctx); - return std::make_unique<impl::OpenSslCryptoCodecImpl>(std::move(ctx_impl), mode); + return std::make_unique<impl::OpenSslCryptoCodecImpl>(std::move(ctx_impl), SocketAddress(), mode); } EncodeResult do_encode(CryptoCodec& codec, Output& buffer, vespalib::stringref plaintext) { diff --git a/vespalib/src/vespa/vespalib/CMakeLists.txt b/vespalib/src/vespa/vespalib/CMakeLists.txt index d13875a9383..4249f6333a4 100644 --- a/vespalib/src/vespa/vespalib/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/CMakeLists.txt @@ -25,7 +25,7 @@ vespa_add_library(vespalib $<TARGET_OBJECTS:vespalib_vespalib_websocket> INSTALL lib64 DEPENDS - gcc + ${VESPA_GCC_LIB} ) vespa_add_target_package_dependency(vespalib OpenSSL) diff --git a/vespalib/src/vespa/vespalib/btree/btreenode.h b/vespalib/src/vespa/vespalib/btree/btreenode.h index 3bcc0fd100f..dd8fe09ec76 100644 --- a/vespalib/src/vespa/vespalib/btree/btreenode.h +++ b/vespalib/src/vespa/vespalib/btree/btreenode.h @@ -161,6 +161,7 @@ public: static const NoAggregated &getEmptyAggregated() { return _instance; } }; +template <> MinMaxAggregated BTreeNodeAggregatedWrap<MinMaxAggregated>::_instance; template <typename KeyT, uint32_t NumSlots> class BTreeNodeT : public BTreeNode { diff --git a/vespalib/src/vespa/vespalib/net/socket_address.cpp b/vespalib/src/vespa/vespalib/net/socket_address.cpp index d71441b0989..1a7a04b2925 100644 --- a/vespalib/src/vespa/vespalib/net/socket_address.cpp +++ b/vespalib/src/vespa/vespalib/net/socket_address.cpp @@ -8,6 +8,7 @@ #include <ifaddrs.h> #include <netdb.h> #include <cassert> +#include <cerrno> namespace vespalib { diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp index 680d6472470..c54990b3782 100644 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp @@ -6,10 +6,12 @@ namespace vespalib::net::tls { -std::unique_ptr<CryptoCodec> CryptoCodec::create_default_codec(std::shared_ptr<TlsContext> ctx, Mode mode) { +std::unique_ptr<CryptoCodec> CryptoCodec::create_default_codec( + std::shared_ptr<TlsContext> ctx, const SocketAddress& peer_address, Mode mode) +{ auto ctx_impl = std::dynamic_pointer_cast<impl::OpenSslTlsContextImpl>(ctx); // only takes by const ref assert(ctx_impl); - return std::make_unique<impl::OpenSslCryptoCodecImpl>(std::move(ctx_impl), mode); + return std::make_unique<impl::OpenSslCryptoCodecImpl>(std::move(ctx_impl), peer_address, mode); } } diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h index fc5303dbc5b..5d9684461d7 100644 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h +++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once +#include <vespa/vespalib/net/socket_address.h> #include <memory> namespace vespalib::net::tls { @@ -178,7 +179,9 @@ public: * * Throws CryptoException if resources cannot be allocated for the codec. */ - static std::unique_ptr<CryptoCodec> create_default_codec(std::shared_ptr<TlsContext> ctx, Mode mode); + static std::unique_ptr<CryptoCodec> create_default_codec(std::shared_ptr<TlsContext> ctx, + const SocketAddress& peer_address, + Mode mode); }; } diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp index 3049c02b798..5315754d53a 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp @@ -17,7 +17,7 @@ #include <openssl/err.h> #include <openssl/pem.h> -#include <vespa/log/log.h> +#include <vespa/log/bufferedlogger.h> LOG_SETUP(".vespalib.net.tls.openssl_crypto_codec_impl"); #if (OPENSSL_VERSION_NUMBER < 0x10000000L) @@ -159,15 +159,23 @@ vespalib::string ssl_error_from_stack() { return vespalib::string(buf); } -void log_ssl_error(const char* source, int ssl_error) { - LOG(error, "%s returned unexpected error: %s (%s)", - source, ssl_error_to_str(ssl_error), ssl_error_from_stack().c_str()); +void log_ssl_error(const char* source, const SocketAddress& peer_address, int ssl_error) { + // Buffer the emitted log messages on the peer's IP address. This prevents a single misbehaving + // client from flooding our logs, while at the same time ensuring that logs for other clients + // aren't lost. + LOGBT(warning, peer_address.ip_address(), + "%s (with peer '%s') returned unexpected error: %s (%s)", + source, peer_address.spec().c_str(), + ssl_error_to_str(ssl_error), ssl_error_from_stack().c_str()); } } // anon ns -OpenSslCryptoCodecImpl::OpenSslCryptoCodecImpl(std::shared_ptr<OpenSslTlsContextImpl> ctx, Mode mode) +OpenSslCryptoCodecImpl::OpenSslCryptoCodecImpl(std::shared_ptr<OpenSslTlsContextImpl> ctx, + const SocketAddress& peer_address, + Mode mode) : _ctx(std::move(ctx)), + _peer_address(peer_address), _ssl(::SSL_new(_ctx->native_context())), _mode(mode), _deferred_handshake_params(), @@ -214,6 +222,10 @@ OpenSslCryptoCodecImpl::OpenSslCryptoCodecImpl(std::shared_ptr<OpenSslTlsContext } else { ::SSL_set_accept_state(_ssl.get()); } + // Store self-reference that can be fished out of SSL object during certificate verification callbacks + if (SSL_set_app_data(_ssl.get(), this) != 1) { + throw CryptoException("SSL_set_app_data() failed"); + } } OpenSslCryptoCodecImpl::~OpenSslCryptoCodecImpl() = default; @@ -302,7 +314,7 @@ HandshakeResult OpenSslCryptoCodecImpl::do_handshake_and_consume_peer_input_byte ConnectionStatistics::get(_mode == Mode::Server).inc_tls_connections(); return handshake_consumed_bytes_and_is_complete(static_cast<size_t>(consumed)); } else { - log_ssl_error("SSL_do_handshake()", ssl_result); + log_ssl_error("SSL_do_handshake()", _peer_address, ssl_result); ConnectionStatistics::get(_mode == Mode::Server).inc_failed_tls_handshakes(); return handshake_failed(); } @@ -327,7 +339,7 @@ EncodeResult OpenSslCryptoCodecImpl::encode(const char* plaintext, size_t plaint // SSL_write encodes plaintext to ciphertext and writes to _output_bio const int consumed = ::SSL_write(_ssl.get(), plaintext, to_consume); if (consumed < 0) { - log_ssl_error("SSL_write()", ::SSL_get_error(_ssl.get(), consumed)); + log_ssl_error("SSL_write()", _peer_address, ::SSL_get_error(_ssl.get(), consumed)); ConnectionStatistics::get(_mode == Mode::Server).inc_broken_tls_connections(); return encode_failed(); // TODO explicitly detect and log TLS renegotiation error (SSL_ERROR_WANT_READ)? } else if (consumed != to_consume) { @@ -391,7 +403,7 @@ DecodeResult OpenSslCryptoCodecImpl::remap_ssl_read_failure_to_decode_result(int LOG(debug, "SSL_read() returned SSL_ERROR_ZERO_RETURN; connection has been shut down normally by the peer"); return decode_peer_has_closed(); default: - log_ssl_error("SSL_read()", ssl_error); + log_ssl_error("SSL_read()", _peer_address, ssl_error); ConnectionStatistics::get(_mode == Mode::Server).inc_broken_tls_connections(); return decode_failed(); } @@ -403,7 +415,7 @@ EncodeResult OpenSslCryptoCodecImpl::half_close(char* ciphertext, size_t ciphert const int pending_before = BIO_pending(_output_bio); int ssl_result = ::SSL_shutdown(_ssl.get()); if (ssl_result < 0) { - log_ssl_error("SSL_shutdown()", ::SSL_get_error(_ssl.get(), ssl_result)); + log_ssl_error("SSL_shutdown()", _peer_address, ::SSL_get_error(_ssl.get(), ssl_result)); return encode_failed(); } const int pending_after = BIO_pending(_output_bio); diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h index ef7e0998994..14200de449a 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h @@ -2,6 +2,7 @@ #pragma once #include "openssl_typedefs.h" +#include <vespa/vespalib/net/socket_address.h> #include <vespa/vespalib/net/tls/transport_security_options.h> #include <vespa/vespalib/net/tls/crypto_codec.h> #include <memory> @@ -45,6 +46,7 @@ class OpenSslCryptoCodecImpl : public CryptoCodec { // The context maintains shared verification callback state, so it must be // kept alive explictly for at least as long as any codecs. std::shared_ptr<OpenSslTlsContextImpl> _ctx; + SocketAddress _peer_address; SslPtr _ssl; ::BIO* _input_bio; // Owned by _ssl ::BIO* _output_bio; // Owned by _ssl @@ -52,7 +54,7 @@ class OpenSslCryptoCodecImpl : public CryptoCodec { std::optional<DeferredHandshakeParams> _deferred_handshake_params; std::optional<HandshakeResult> _deferred_handshake_result; public: - OpenSslCryptoCodecImpl(std::shared_ptr<OpenSslTlsContextImpl> ctx, Mode mode); + OpenSslCryptoCodecImpl(std::shared_ptr<OpenSslTlsContextImpl> ctx, const SocketAddress& peer_address, Mode mode); ~OpenSslCryptoCodecImpl() override; /* @@ -85,6 +87,8 @@ public: DecodeResult decode(const char* ciphertext, size_t ciphertext_size, char* plaintext, size_t plaintext_size) noexcept override; EncodeResult half_close(char* ciphertext, size_t ciphertext_size) noexcept override; + + const SocketAddress& peer_address() const noexcept { return _peer_address; } private: HandshakeResult do_handshake_and_consume_peer_input_bytes() noexcept; DecodeResult drain_and_produce_plaintext_from_ssl(char* plaintext, size_t plaintext_size) noexcept; diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp index c87dc1d2148..98675ec6b0b 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp @@ -2,6 +2,7 @@ #include "iana_cipher_map.h" #include "openssl_typedefs.h" #include "openssl_tls_context_impl.h" +#include "openssl_crypto_codec_impl.h" #include <vespa/vespalib/net/tls/crypto_exception.h> #include <vespa/vespalib/net/tls/statistics.h> #include <vespa/vespalib/net/tls/transport_security_options.h> @@ -17,7 +18,7 @@ #include <openssl/asn1.h> #include <openssl/pem.h> -#include <vespa/log/log.h> +#include <vespa/log/bufferedlogger.h> LOG_SETUP(".vespalib.net.tls.openssl_tls_context_impl"); #if (OPENSSL_VERSION_NUMBER < 0x10000000L) @@ -402,8 +403,6 @@ bool fill_certificate_subject_alternate_names(::X509* cert, PeerCredentials& cre } // anon ns -// TODO if/when we want to move per-connection peer credentials into the crypto codec/socket -// itself, we probably need to set the verification callback (data) on _SSL_, not _SSL_CTX_..! // Note: we try to be as conservative as possible. If anything looks out of place, we fail // secure by denying the connection. // @@ -426,19 +425,22 @@ int OpenSslTlsContextImpl::verify_cb_wrapper(int preverified_ok, ::X509_STORE_CT void* data = ::X509_STORE_CTX_get_ex_data(store_ctx, ::SSL_get_ex_data_X509_STORE_CTX_idx()); LOG_ASSERT(data != nullptr); auto* ssl = static_cast<::SSL*>(data); + data = SSL_get_app_data(ssl); + LOG_ASSERT(data != nullptr); + auto* codec_impl = static_cast<OpenSslCryptoCodecImpl*>(data); const ::SSL_CTX* ssl_ctx = ::SSL_get_SSL_CTX(ssl); LOG_ASSERT(ssl_ctx != nullptr); auto* self = static_cast<OpenSslTlsContextImpl*>(SSL_CTX_get_app_data(ssl_ctx)); LOG_ASSERT(self != nullptr); - if (self->verify_trusted_certificate(store_ctx)) { + if (self->verify_trusted_certificate(store_ctx, codec_impl->peer_address())) { return 1; } ConnectionStatistics::get(SSL_in_accept_init(ssl) != 0).inc_invalid_peer_credentials(); return 0; } -bool OpenSslTlsContextImpl::verify_trusted_certificate(::X509_STORE_CTX* store_ctx) { +bool OpenSslTlsContextImpl::verify_trusted_certificate(::X509_STORE_CTX* store_ctx, const SocketAddress& peer_address) { const auto authz_mode = authorization_mode(); // TODO consider if we want to fill in peer credentials even if authorization is disabled if (authz_mode == AuthorizationMode::Disable) { @@ -459,13 +461,16 @@ bool OpenSslTlsContextImpl::verify_trusted_certificate(::X509_STORE_CTX* store_c try { const bool verified_by_cb = _cert_verify_callback->verify(creds); if (!verified_by_cb) { - // TODO we should print the peer's remote address too, but that information is - // not currently available to us here. - LOG(warning, "Certificate verification failed for %s", to_string(creds).c_str()); + // Buffer warnings on peer IP address to avoid log flooding. + LOGBT(warning, peer_address.ip_address(), + "Certificate verification of peer '%s' failed with %s", + peer_address.spec().c_str(), to_string(creds).c_str()); return (authz_mode != AuthorizationMode::Enforce); } } catch (std::exception& e) { - LOG(error, "Got exception during certificate verification callback: %s", e.what()); + LOGBT(error, peer_address.ip_address(), + "Got exception during certificate verification callback for peer '%s': %s", + peer_address.spec().c_str(), e.what()); return false; } // we don't expect any non-std::exception derived exceptions, so let them terminate the process. return true; diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h index 31814dad8ba..c519b1ae874 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h @@ -2,6 +2,7 @@ #pragma once #include "openssl_typedefs.h" +#include <vespa/vespalib/net/socket_address.h> #include <vespa/vespalib/net/tls/tls_context.h> #include <vespa/vespalib/net/tls/transport_security_options.h> #include <vespa/vespalib/net/tls/certificate_verification_callback.h> @@ -46,7 +47,7 @@ private: void set_ssl_ctx_self_reference(); void set_accepted_cipher_suites(const std::vector<vespalib::string>& ciphers); - bool verify_trusted_certificate(::X509_STORE_CTX* store_ctx); + bool verify_trusted_certificate(::X509_STORE_CTX* store_ctx, const SocketAddress& peer_address); static int verify_cb_wrapper(int preverified_ok, ::X509_STORE_CTX* store_ctx); }; diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp index 7d0d3287965..58d99cc7108 100644 --- a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp @@ -15,7 +15,7 @@ std::unique_ptr<TlsCryptoSocket> TlsCryptoEngine::create_tls_crypto_socket(SocketHandle socket, bool is_server) { auto mode = is_server ? net::tls::CryptoCodec::Mode::Server : net::tls::CryptoCodec::Mode::Client; - auto codec = net::tls::CryptoCodec::create_default_codec(_tls_ctx, mode); + auto codec = net::tls::CryptoCodec::create_default_codec(_tls_ctx, SocketAddress::peer_address(socket.get()), mode); return std::make_unique<net::tls::CryptoCodecAdapter>(std::move(socket), std::move(codec)); } diff --git a/vespalib/src/vespa/vespalib/stllike/hashtable.cpp b/vespalib/src/vespa/vespalib/stllike/hashtable.cpp index 9c0db3ae817..52639fd6275 100644 --- a/vespalib/src/vespa/vespalib/stllike/hashtable.cpp +++ b/vespalib/src/vespa/vespalib/stllike/hashtable.cpp @@ -1,5 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include <vespa/vespalib/stllike/hashtable.hpp> +#include <algorithm> namespace { diff --git a/vespamalloc/src/tests/doubledelete/doubledelete.cpp b/vespamalloc/src/tests/doubledelete/doubledelete.cpp index aa8d9b1204f..299583456cf 100644 --- a/vespamalloc/src/tests/doubledelete/doubledelete.cpp +++ b/vespamalloc/src/tests/doubledelete/doubledelete.cpp @@ -1,11 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include <stdlib.h> +void *savedptr; + int main(int argc, char *argv[]) { (void) argc; (void) argv; - char * a = new char [100]; + char * a = new char; + savedptr = a; delete a; delete a; } diff --git a/vespamalloc/src/tests/test1/testatomic.cpp b/vespamalloc/src/tests/test1/testatomic.cpp index 54a0b406116..9e44bc8a913 100644 --- a/vespamalloc/src/tests/test1/testatomic.cpp +++ b/vespamalloc/src/tests/test1/testatomic.cpp @@ -14,12 +14,8 @@ TEST("verify lock freeness of atomics"){ { std::atomic<vespamalloc::TaggedPtr> taggedPtr; ASSERT_EQUAL(16u, sizeof(vespamalloc::TaggedPtr)); -#if __GNUC__ < 7 // See https://gcc.gnu.org/ml/gcc-patches/2017-01/msg02344.html for background - ASSERT_TRUE(taggedPtr.is_lock_free()); -#else ASSERT_TRUE(taggedPtr.is_lock_free() || !taggedPtr.is_lock_free()); -#endif } } diff --git a/vespamalloc/src/vespamalloc/malloc/common.h b/vespamalloc/src/vespamalloc/malloc/common.h index 9cc3d319e59..b21a2f63ed5 100644 --- a/vespamalloc/src/vespamalloc/malloc/common.h +++ b/vespamalloc/src/vespamalloc/malloc/common.h @@ -74,7 +74,7 @@ public: static inline size_t classSize(SizeClassT sc) { return (size_t(1) << (sc + MinClassSizeC)); } }; -inline void crash() { *((unsigned *) NULL) = 0; } +inline void crash() { *((volatile unsigned *) NULL) = 0; } template <typename T> inline void swap(T & a, T & b) { T tmp(a); a = b; b = tmp; } diff --git a/vespamalloc/src/vespamalloc/malloc/globalpool.h b/vespamalloc/src/vespamalloc/malloc/globalpool.h index 6a05d14a901..bc13231bb85 100644 --- a/vespamalloc/src/vespamalloc/malloc/globalpool.h +++ b/vespamalloc/src/vespamalloc/malloc/globalpool.h @@ -27,10 +27,7 @@ public: DataSegment<MemBlockPtrT> & dataSegment() { return _dataSegment; } void enableThreadSupport() __attribute__((noinline)); - static void setParams(size_t alwaysReuseLimit, size_t threadCacheLimit) { - _alwaysReuseLimit = alwaysReuseLimit; - _threadCacheLimit = threadCacheLimit; - } + static void setParams(size_t alwaysReuseLimit, size_t threadCacheLimit); void info(FILE * os, size_t level=0) __attribute__((noinline)); private: diff --git a/vespamalloc/src/vespamalloc/malloc/globalpool.hpp b/vespamalloc/src/vespamalloc/malloc/globalpool.hpp index 5cc172750c5..c954c1aae26 100644 --- a/vespamalloc/src/vespamalloc/malloc/globalpool.hpp +++ b/vespamalloc/src/vespamalloc/malloc/globalpool.hpp @@ -35,6 +35,14 @@ void AllocPoolT<MemBlockPtrT>::enableThreadSupport() } template <typename MemBlockPtrT> +void +AllocPoolT<MemBlockPtrT>::setParams(size_t alwaysReuseLimit, size_t threadCacheLimit) +{ + _alwaysReuseLimit = alwaysReuseLimit; + _threadCacheLimit = threadCacheLimit; +} + +template <typename MemBlockPtrT> typename AllocPoolT<MemBlockPtrT>::ChunkSList * AllocPoolT<MemBlockPtrT>::getFree(SizeClassT sc) { diff --git a/vespamalloc/src/vespamalloc/malloc/memblock.h b/vespamalloc/src/vespamalloc/malloc/memblock.h index 85dd725749c..118fb0e046c 100644 --- a/vespamalloc/src/vespamalloc/malloc/memblock.h +++ b/vespamalloc/src/vespamalloc/malloc/memblock.h @@ -45,7 +45,7 @@ public: static size_t unAdjustSize(size_t sz) { return sz; } static void dumpInfo(size_t level); static void dumpFile(FILE * fp) { _logFile = fp; } - static void bigBlockLimit(size_t lim) { _bigBlockLimit = lim; } + static void bigBlockLimit(size_t lim); static void setFill(uint8_t ) { } static bool verifySizeClass(int sc) { (void) sc; return true; } static size_t getMinSizeForAlignment(size_t align, size_t sz) { @@ -60,6 +60,8 @@ private: }; typedef MemBlockT<5, 20> MemBlock; +template <> void MemBlock::dumpInfo(size_t level); +extern template class MemBlockT<5, 20>; } diff --git a/vespamalloc/src/vespamalloc/malloc/memblock.hpp b/vespamalloc/src/vespamalloc/malloc/memblock.hpp index e6df3bcc67e..57b5a8cac51 100644 --- a/vespamalloc/src/vespamalloc/malloc/memblock.hpp +++ b/vespamalloc/src/vespamalloc/malloc/memblock.hpp @@ -29,6 +29,13 @@ MemBlockT<MinSizeClassC, MaxSizeClassMultiAllocC>::logBigBlock(size_t exact, siz } template <size_t MinSizeClassC, size_t MaxSizeClassMultiAllocC> +void +MemBlockT<MinSizeClassC, MaxSizeClassMultiAllocC>::bigBlockLimit(size_t lim) +{ + _bigBlockLimit = lim; +} + +template <size_t MinSizeClassC, size_t MaxSizeClassMultiAllocC> FILE * MemBlockT<MinSizeClassC, MaxSizeClassMultiAllocC>::_logFile = stderr; template <size_t MinSizeClassC, size_t MaxSizeClassMultiAllocC> size_t MemBlockT<MinSizeClassC, MaxSizeClassMultiAllocC>::_bigBlockLimit = 0x80000000; diff --git a/vespamalloc/src/vespamalloc/malloc/overload.h b/vespamalloc/src/vespamalloc/malloc/overload.h index 1845703aad8..7883578cc28 100644 --- a/vespamalloc/src/vespamalloc/malloc/overload.h +++ b/vespamalloc/src/vespamalloc/malloc/overload.h @@ -13,6 +13,9 @@ public: vespamalloc::createAllocator(); } private: +#ifdef __clang__ + [[maybe_unused]] +#endif unsigned _initialized; }; @@ -119,8 +122,8 @@ int posix_memalign(void** ptr, size_t align, size_t sz) __THROW return retval; } -void *valloc(size_t size) __attribute__((visibility ("default"))); -void *valloc(size_t size) +void *valloc(size_t size) __THROW __attribute__((visibility ("default"))); +void *valloc(size_t size) __THROW { return memalign(sysconf(_SC_PAGESIZE),size); } @@ -131,12 +134,24 @@ void free(void * ptr) { } #define ALIAS(x) __attribute__ ((weak, alias (x), visibility ("default"))) +#ifdef __clang__ +void* __libc_malloc(size_t sz) __THROW __attribute__((malloc, alloc_size(1))) ALIAS("malloc"); +void* __libc_realloc(void* ptr, size_t sz) __THROW __attribute__((malloc, alloc_size(2))) ALIAS("realloc"); +void* __libc_calloc(size_t n, size_t sz) __THROW __attribute__((malloc, alloc_size(2))) ALIAS("calloc"); +void cfree(void *) __THROW ALIAS("free"); +void __libc_free(void* ptr) __THROW ALIAS("free"); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wignored-attributes" +void __libc_cfree(void* ptr) __THROW ALIAS("cfree"); +#pragma clang diagnostic pop +#else void* __libc_malloc(size_t sz) __THROW __attribute__((leaf, malloc, alloc_size(1))) ALIAS("malloc"); void* __libc_realloc(void* ptr, size_t sz) __THROW __attribute__((leaf, malloc, alloc_size(2))) ALIAS("realloc"); void* __libc_calloc(size_t n, size_t sz) __THROW __attribute__((leaf, malloc, alloc_size(2))) ALIAS("calloc"); void cfree(void *) __THROW __attribute__((leaf)) ALIAS("free"); void __libc_free(void* ptr) __THROW __attribute__((leaf)) ALIAS("free"); void __libc_cfree(void* ptr) __THROW __attribute__((leaf)) ALIAS("cfree"); +#endif void* __libc_memalign(size_t align, size_t s) ALIAS("memalign"); int __posix_memalign(void** r, size_t a, size_t s) __THROW __nonnull((1)) ALIAS("posix_memalign"); #undef ALIAS diff --git a/vespamalloc/src/vespamalloc/malloc/threadlist.h b/vespamalloc/src/vespamalloc/malloc/threadlist.h index a8f28ebcb67..6e518091f0f 100644 --- a/vespamalloc/src/vespamalloc/malloc/threadlist.h +++ b/vespamalloc/src/vespamalloc/malloc/threadlist.h @@ -48,4 +48,7 @@ private: static __thread ThreadPool * _myPool TLS_LINKAGE; }; +template <typename MemBlockPtrT, typename ThreadStatT> +__thread ThreadPoolT<MemBlockPtrT, ThreadStatT> * ThreadListT<MemBlockPtrT, ThreadStatT>::_myPool TLS_LINKAGE = nullptr; + } diff --git a/vespamalloc/src/vespamalloc/malloc/threadlist.hpp b/vespamalloc/src/vespamalloc/malloc/threadlist.hpp index ebeb23b2883..e437981febc 100644 --- a/vespamalloc/src/vespamalloc/malloc/threadlist.hpp +++ b/vespamalloc/src/vespamalloc/malloc/threadlist.hpp @@ -73,7 +73,5 @@ bool ThreadListT<MemBlockPtrT, ThreadStatT>::initThisThread() return retval; } -template <typename MemBlockPtrT, typename ThreadStatT> -__thread ThreadPoolT<MemBlockPtrT, ThreadStatT> * ThreadListT<MemBlockPtrT, ThreadStatT>::_myPool TLS_LINKAGE = NULL; } diff --git a/vsm/src/tests/searcher/searcher.cpp b/vsm/src/tests/searcher/searcher.cpp index f5f7508a256..02555f9d6f6 100644 --- a/vsm/src/tests/searcher/searcher.cpp +++ b/vsm/src/tests/searcher/searcher.cpp @@ -766,7 +766,7 @@ TEST("FieldSearchSpec constrution") { EXPECT_EQUAL(0x100000u, f.maxLength()); } { - FieldSearchSpec f(7, "f0", VsmfieldsConfig::Fieldspec::AUTOUTF8, "substring", 789); + FieldSearchSpec f(7, "f0", VsmfieldsConfig::Fieldspec::Searchmethod::AUTOUTF8, "substring", 789); EXPECT_TRUE(f.valid()); EXPECT_EQUAL(7u, f.id()); EXPECT_EQUAL("f0", f.name()); @@ -777,8 +777,8 @@ TEST("FieldSearchSpec constrution") { TEST("snippet modifier manager") { FieldSearchSpecMapT specMap; - specMap[0] = FieldSearchSpec(0, "f0", VsmfieldsConfig::Fieldspec::AUTOUTF8, "substring", 1000); - specMap[1] = FieldSearchSpec(1, "f1", VsmfieldsConfig::Fieldspec::AUTOUTF8, "", 1000); + specMap[0] = FieldSearchSpec(0, "f0", VsmfieldsConfig::Fieldspec::Searchmethod::AUTOUTF8, "substring", 1000); + specMap[1] = FieldSearchSpec(1, "f1", VsmfieldsConfig::Fieldspec::Searchmethod::AUTOUTF8, "", 1000); IndexFieldMapT indexMap; indexMap["i0"].push_back(0); indexMap["i1"].push_back(1); diff --git a/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp b/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp index c5e63fde7ba..1440b894934 100644 --- a/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp +++ b/vsm/src/vespa/vsm/vsm/docsumfieldspec.cpp @@ -16,7 +16,7 @@ DocsumFieldSpec::FieldIdentifier::FieldIdentifier(FieldIdT id, FieldPath path) : DocsumFieldSpec::DocsumFieldSpec() : _resultType(search::docsummary::RES_INT), - _command(VsmsummaryConfig::Fieldmap::NONE), + _command(VsmsummaryConfig::Fieldmap::Command::NONE), _outputField(), _inputFields() { } diff --git a/vsm/src/vespa/vsm/vsm/docsumfilter.cpp b/vsm/src/vespa/vsm/vsm/docsumfilter.cpp index 872ccf6acf8..3794e78cf5b 100644 --- a/vsm/src/vespa/vsm/vsm/docsumfilter.cpp +++ b/vsm/src/vespa/vsm/vsm/docsumfilter.cpp @@ -188,7 +188,7 @@ DocsumFilter::getFieldValue(const DocsumFieldSpec::FieldIdentifier & fieldId, return nullptr; } switch (command) { - case VsmsummaryConfig::Fieldmap::FLATTENJUNIPER: + case VsmsummaryConfig::Fieldmap::Command::FLATTENJUNIPER: if (_snippetModifiers != nullptr) { FieldModifier * mod = _snippetModifiers->getModifier(fId); if (mod != nullptr) { @@ -319,7 +319,7 @@ DocsumFilter::writeSlimeField(const DocsumFieldSpec & fieldSpec, const Document & docsum, ResultPacker & packer) { - if (fieldSpec.getCommand() == VsmsummaryConfig::Fieldmap::NONE) { + if (fieldSpec.getCommand() == VsmsummaryConfig::Fieldmap::Command::NONE) { const DocsumFieldSpec::FieldIdentifier & fieldId = fieldSpec.getOutputField(); const document::FieldValue * fv = docsum.getField(fieldId.getId()); if (fv != nullptr) { @@ -347,7 +347,7 @@ DocsumFilter::writeFlattenField(const DocsumFieldSpec & fieldSpec, const Document & docsum, ResultPacker & packer) { - if (fieldSpec.getCommand() == VsmsummaryConfig::Fieldmap::NONE) { + if (fieldSpec.getCommand() == VsmsummaryConfig::Fieldmap::Command::NONE) { LOG(debug, "writeFlattenField: Cannot handle command NONE"); packer.AddEmpty(); return; @@ -362,7 +362,7 @@ DocsumFilter::writeFlattenField(const DocsumFieldSpec & fieldSpec, } switch (fieldSpec.getCommand()) { - case VsmsummaryConfig::Fieldmap::FLATTENJUNIPER: + case VsmsummaryConfig::Fieldmap::Command::FLATTENJUNIPER: _flattenWriter.setSeparator("\x1E"); // record separator (same as juniper uses) break; default: @@ -453,7 +453,7 @@ DocsumFilter::getMappedDocsum(uint32_t id) // this really means 'structured data' writeSlimeField(*it, doc, _packer); } else { - if (it->getInputFields().size() == 1 && it->getCommand() == VsmsummaryConfig::Fieldmap::NONE) { + if (it->getInputFields().size() == 1 && it->getCommand() == VsmsummaryConfig::Fieldmap::Command::NONE) { const DocsumFieldSpec::FieldIdentifier & fieldId = it->getInputFields()[0]; const document::FieldValue * field = doc.getField(fieldId.getId()); if (field != nullptr) { @@ -461,7 +461,7 @@ DocsumFilter::getMappedDocsum(uint32_t id) } else { writeEmpty(type, _packer); // void input } - } else if (it->getInputFields().size() == 0 && it->getCommand() == VsmsummaryConfig::Fieldmap::NONE) { + } else if (it->getInputFields().size() == 0 && it->getCommand() == VsmsummaryConfig::Fieldmap::Command::NONE) { LOG(spam, "0 inputfields for output field %u", it->getOutputField().getId()); writeEmpty(type, _packer); // no input } else { diff --git a/vsm/src/vespa/vsm/vsm/fieldsearchspec.cpp b/vsm/src/vespa/vsm/vsm/fieldsearchspec.cpp index bb30cdd89e9..8d4cf72b824 100644 --- a/vsm/src/vespa/vsm/vsm/fieldsearchspec.cpp +++ b/vsm/src/vespa/vsm/vsm/fieldsearchspec.cpp @@ -45,7 +45,7 @@ FieldSearchSpec::FieldSearchSpec() : _name(), _maxLength(0x100000), _searcher(), - _searchMethod(VsmfieldsConfig::Fieldspec::NONE), + _searchMethod(VsmfieldsConfig::Fieldspec::Searchmethod::NONE), _arg1(), _reconfigured(false) { @@ -65,12 +65,12 @@ FieldSearchSpec::FieldSearchSpec(const FieldIdT & fid, const vespalib::string & { switch(searchDef) { default: - LOG(warning, "Unknown searchdef = %d. Defaulting to AUTOUTF8", searchDef); + LOG(warning, "Unknown searchdef = %d. Defaulting to AUTOUTF8", static_cast<int>(searchDef)); [[fallthrough]]; - case VsmfieldsConfig::Fieldspec::AUTOUTF8: - case VsmfieldsConfig::Fieldspec::NONE: - case VsmfieldsConfig::Fieldspec::SSE2UTF8: - case VsmfieldsConfig::Fieldspec::UTF8: + case VsmfieldsConfig::Fieldspec::Searchmethod::AUTOUTF8: + case VsmfieldsConfig::Fieldspec::Searchmethod::NONE: + case VsmfieldsConfig::Fieldspec::Searchmethod::SSE2UTF8: + case VsmfieldsConfig::Fieldspec::Searchmethod::UTF8: if (arg1 == "substring") { _searcher = UTF8SubStringFieldSearcher(fid); } else if (arg1 == "suffix") { @@ -79,25 +79,25 @@ FieldSearchSpec::FieldSearchSpec(const FieldIdT & fid, const vespalib::string & _searcher = UTF8ExactStringFieldSearcher(fid); } else if (arg1 == "word") { _searcher = UTF8ExactStringFieldSearcher(fid); - } else if (searchDef == VsmfieldsConfig::Fieldspec::UTF8) { + } else if (searchDef == VsmfieldsConfig::Fieldspec::Searchmethod::UTF8) { _searcher = UTF8StrChrFieldSearcher(fid); } else { _searcher = FUTF8StrChrFieldSearcher(fid); } break; - case VsmfieldsConfig::Fieldspec::BOOL: + case VsmfieldsConfig::Fieldspec::Searchmethod::BOOL: _searcher = BoolFieldSearcher(fid); break; - case VsmfieldsConfig::Fieldspec::INT8: - case VsmfieldsConfig::Fieldspec::INT16: - case VsmfieldsConfig::Fieldspec::INT32: - case VsmfieldsConfig::Fieldspec::INT64: + case VsmfieldsConfig::Fieldspec::Searchmethod::INT8: + case VsmfieldsConfig::Fieldspec::Searchmethod::INT16: + case VsmfieldsConfig::Fieldspec::Searchmethod::INT32: + case VsmfieldsConfig::Fieldspec::Searchmethod::INT64: _searcher = IntFieldSearcher(fid); break; - case VsmfieldsConfig::Fieldspec::FLOAT: + case VsmfieldsConfig::Fieldspec::Searchmethod::FLOAT: _searcher = FloatFieldSearcher(fid); break; - case VsmfieldsConfig::Fieldspec::DOUBLE: + case VsmfieldsConfig::Fieldspec::Searchmethod::DOUBLE: _searcher = DoubleFieldSearcher(fid); break; } @@ -114,10 +114,10 @@ FieldSearchSpec::reconfig(const search::QueryTerm & term) return; } switch (_searchMethod) { - case VsmfieldsConfig::Fieldspec::NONE: - case VsmfieldsConfig::Fieldspec::AUTOUTF8: - case VsmfieldsConfig::Fieldspec::UTF8: - case VsmfieldsConfig::Fieldspec::SSE2UTF8: + case VsmfieldsConfig::Fieldspec::Searchmethod::NONE: + case VsmfieldsConfig::Fieldspec::Searchmethod::AUTOUTF8: + case VsmfieldsConfig::Fieldspec::Searchmethod::UTF8: + case VsmfieldsConfig::Fieldspec::Searchmethod::SSE2UTF8: if ((term.isSubstring() && _arg1 != "substring") || (term.isSuffix() && _arg1 != "suffix") || (term.isExactstring() && _arg1 != "exact") || diff --git a/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp b/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp index 1cd35e7ca61..f87c327e86a 100644 --- a/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp +++ b/vsm/src/vespa/vsm/vsm/vsm-adapter.cpp @@ -51,7 +51,7 @@ GetDocsumsStateCallback::~GetDocsumsStateCallback() = default; DocsumTools::FieldSpec::FieldSpec() : _outputName(), _inputNames(), - _command(VsmsummaryConfig::Fieldmap::NONE) + _command(VsmsummaryConfig::Fieldmap::Command::NONE) { } DocsumTools::FieldSpec::~FieldSpec() = default; |