From a7224b0d11d1536f8c5a809e616b9a3087385eee Mon Sep 17 00:00:00 2001 From: donoghuc Date: Fri, 24 Jan 2025 13:25:15 -0800 Subject: [PATCH] Pattern for testing using standarized JVM config In a previous iteration jvm options were attempted to be configured at the gradle level. This presented a challeng in rspec loading because webmock was not being loaded correctly with the fips providers. That initial approach was also problematic because it introduced a fork in configuration changes between configuring the JVM via the enviornment variables and files for running logstash in the container vs in the tests. This new apprach attempts to separate aout all of the test setup and building in gradle from a pure "just run the tests" task. The fundamental idea is that we dont want to use FIPS mode for downloading dependencies and building/preparing an environment we only want that configured at the very end. This apprach accomplishes that by teasing out the dependencies that trigger downloads etc in gradel from tasks that will only run the tests. The dockerfile order will call all the gradle tasks for setup, then configure FIPS mode and call the tests that should be run under fips mode. --- Dockerfile | 42 +++++++-------- build.gradle | 13 +++-- logstash-core/build.gradle | 101 +++++++++++++++++-------------------- rubyUtils.gradle | 1 + test-init.gradle | 47 ----------------- 5 files changed, 76 insertions(+), 128 deletions(-) delete mode 100644 test-init.gradle diff --git a/Dockerfile b/Dockerfile index 146f3e375df..3b7d9a9966e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,6 @@ RUN mkdir -p /root/.gradle # Copy configuration files COPY java.security /etc/java/security/ COPY java.policy /etc/java/security/ -COPY test-init.gradle /tmp/test-init.gradle # Set environment variables ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk @@ -27,14 +26,15 @@ COPY . . # Initial build using JKS truststore # ENV JAVA_OPTS="-Djavax.net.ssl.trustStore=$JAVA_HOME/lib/security/cacerts -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStorePassword=changeit" -RUN ./gradlew clean bootstrap assemble installDefaultGems --no-daemon +RUN ./gradlew clean bootstrap assemble installDefaultGems testSetup --no-daemon +# CMD ["sleep", "infinity"] RUN keytool -importkeystore \ -srckeystore $JAVA_HOME/lib/security/cacerts \ -destkeystore /etc/java/security/cacerts.bcfks \ -srcstoretype jks \ -deststoretype bcfks \ - -providerpath /root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bc-fips/2.0.0/ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab/bc-fips-2.0.0.jar \ + -providerpath /logstash/logstash-core/lib/jars/bc-fips-2.0.0.jar \ -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ -deststorepass changeit \ -srcstorepass changeit \ @@ -45,31 +45,33 @@ RUN keytool -importkeystore \ -destkeystore /etc/java/security/keystore.bcfks \ -srcstoretype jks \ -deststoretype bcfks \ - -providerpath /root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bc-fips/2.0.0/ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab/bc-fips-2.0.0.jar \ + -providerpath /logstash/logstash-core/lib/jars/bc-fips-2.0.0.jar \ -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ -deststorepass changeit \ -srcstorepass changeit \ -noprompt + # Uncomment this out and build for interactive terminal sessions in a container # Currently this dockerfile is just for running tests, but I have been also using it to # do manual validation. Need to think more about how to separate concerns. -# ENV JAVA_SECURITY_PROPERTIES=/etc/java/security/java.security -# ENV JAVA_OPTS="\ -# -Djava.security.debug=ssl,provider \ -# -Djava.security.properties=${JAVA_SECURITY_PROPERTIES} \ -# -Djavax.net.ssl.keyStore=/etc/java/security/keystore.bcfks \ -# -Djavax.net.ssl.keyStoreType=BCFKS \ -# -Djavax.net.ssl.keyStoreProvider=BCFIPS \ -# -Djavax.net.ssl.keyStorePassword=changeit \ -# -Djavax.net.ssl.trustStore=/etc/java/security/cacerts.bcfks \ -# -Djavax.net.ssl.trustStoreType=BCFKS \ -# -Djavax.net.ssl.trustStoreProvider=BCFIPS \ -# -Djavax.net.ssl.trustStorePassword=changeit \ -# -Dssl.KeyManagerFactory.algorithm=PKIX \ -# -Dssl.TrustManagerFactory.algorithm=PKIX \ -# -Dorg.bouncycastle.fips.approved_only=true" +ENV JAVA_SECURITY_PROPERTIES=/etc/java/security/java.security +ENV JAVA_OPTS="\ + -Djava.security.debug=ssl,provider \ + -Djava.security.properties=${JAVA_SECURITY_PROPERTIES} \ + -Djavax.net.ssl.keyStore=/etc/java/security/keystore.bcfks \ + -Djavax.net.ssl.keyStoreType=BCFKS \ + -Djavax.net.ssl.keyStoreProvider=BCFIPS \ + -Djavax.net.ssl.keyStorePassword=changeit \ + -Djavax.net.ssl.trustStore=/etc/java/security/cacerts.bcfks \ + -Djavax.net.ssl.trustStoreType=BCFKS \ + -Djavax.net.ssl.trustStoreProvider=BCFIPS \ + -Djavax.net.ssl.trustStorePassword=changeit \ + -Dssl.KeyManagerFactory.algorithm=PKIX \ + -Dssl.TrustManagerFactory.algorithm=PKIX \ + -Dorg.bouncycastle.fips.approved_only=true" +ENV LS_JAVA_OPTS="${JAVA_OPTS}" # Run tests with BCFKS truststore -CMD ["./gradlew", "--info", "--stacktrace", "test"] +CMD ["./gradlew","-PskipJRubySetup=true", "--info", "--stacktrace", "javaTestsOnly", "rubyTestsOnly"] diff --git a/build.gradle b/build.gradle index 3f865ab7686..1410899332b 100644 --- a/build.gradle +++ b/build.gradle @@ -295,15 +295,18 @@ clean { delete "${projectDir}/logstash-core/src/main/resources/org/logstash/plugins/plugin_aliases.yml" } -def assemblyDeps = [downloadAndInstallJRuby, assemble] + subprojects.collect { - it.tasks.findByName("assemble") +def getAssemblyDeps() { + return project.hasProperty('skipJRubySetup') ? [] : [downloadAndInstallJRuby, assemble] + subprojects.collect { + it.tasks.findByName("assemble") + } } tasks.register("bootstrap") { - dependsOn assemblyDeps + onlyIf { !project.hasProperty('skipJRubySetup') } + dependsOn getAssemblyDeps() doLast { - setupJruby(projectDir, buildDir) - } + setupJruby(projectDir, buildDir) + } } diff --git a/logstash-core/build.gradle b/logstash-core/build.gradle index 5c10d86df63..713c057dd28 100644 --- a/logstash-core/build.gradle +++ b/logstash-core/build.gradle @@ -27,27 +27,12 @@ buildscript { } } -plugins { - id "jacoco" - id "org.sonarqube" version "4.3.0.3225" -} -apply plugin: 'jacoco' -apply plugin: "org.sonarqube" repositories { mavenCentral() } -sonarqube { - properties { - property 'sonar.coverage.jacoco.xmlReportPaths', "${buildDir}/reports/jacoco/test/jacocoTestReport.xml" - } -} - -jacoco { - toolVersion = "0.8.9" -} import org.yaml.snakeyaml.Yaml @@ -110,26 +95,7 @@ configurations.archives { extendsFrom configurations.javadoc } -def setupBCProvider() { - println "Setting up BC provider" - System.setProperty("javax.net.ssl.trustStore", "/etc/java/security/cacerts.bcfks") - System.setProperty("javax.net.ssl.trustStoreType", "BCFKS") - System.setProperty("javax.net.ssl.trustStoreProvider", "BCFIPS") - System.setProperty("javax.net.ssl.trustStorePassword", "changeit") - - def provider = Class.forName('org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider').getDeclaredConstructor().newInstance() - java.security.Security.insertProviderAt(provider, 1) -} - tasks.register("javaTests", Test) { - doFirst { - configurations.testRuntimeClasspath.resolvedConfiguration.files.each { file -> - if (file.name.startsWith("bc-")) { - println "Found BC jar: ${file}" - } - } - setupBCProvider() - } dependsOn ':bootstrap' exclude '/org/logstash/RSpecTests.class' exclude '/org/logstash/config/ir/ConfigCompilerTest.class' @@ -143,31 +109,10 @@ tasks.register("javaTests", Test) { exclude '/org/logstash/plugins/factory/PluginFactoryExtTest.class' exclude '/org/logstash/execution/ObservedExecutionTest.class' - jacoco { - enabled = true - destinationFile = layout.buildDirectory.file('jacoco/test.exec').get().asFile - classDumpDir = layout.buildDirectory.dir('jacoco/classpathdumps').get().asFile - } } -jacocoTestReport { - reports { - xml.required = true - html.required = true - } -} - -javaTests.finalizedBy(jacocoTestReport) tasks.register("rubyTests", Test) { - doFirst { - configurations.testRuntimeClasspath.resolvedConfiguration.files.each { file -> - if (file.name.startsWith("bc-")) { - println "Found BC jar: ${file}" - } - } - setupBCProvider() - } dependsOn compileTestJava inputs.files fileTree("${projectDir}/lib") inputs.files fileTree("${projectDir}/spec") @@ -223,6 +168,50 @@ task generateVersionInfoResources(type: DefaultTask) { resourceFile.text = "logstash-core: ${logstashCoreVersion}" } } +// Test execution tasks without dependencies +tasks.register("javaTestsOnly", Test) { + setDependsOn([]) + exclude '/org/logstash/RSpecTests.class' + exclude '/org/logstash/config/ir/ConfigCompilerTest.class' + exclude '/org/logstash/config/ir/CompiledPipelineTest.class' + exclude '/org/logstash/config/ir/EventConditionTest.class' + exclude '/org/logstash/config/ir/PipelineConfigTest.class' + exclude '/org/logstash/config/ir/compiler/OutputDelegatorTest.class' + exclude '/org/logstash/config/ir/compiler/JavaCodecDelegatorTest.class' + exclude '/org/logstash/plugins/NamespacedMetricImplTest.class' + exclude '/org/logstash/plugins/CounterMetricImplTest.class' + exclude '/org/logstash/plugins/factory/PluginFactoryExtTest.class' + exclude '/org/logstash/execution/ObservedExecutionTest.class' +} + +tasks.register("rubyTestsOnly", Test) { + setDependsOn([]) + inputs.files fileTree("${projectDir}/lib") + inputs.files fileTree("${projectDir}/spec") + systemProperty 'logstash.root.dir', projectDir.parent + + include '/org/logstash/RSpecTests.class' + include '/org/logstash/config/ir/ConfigCompilerTest.class' + include '/org/logstash/config/ir/CompiledPipelineTest.class' + include '/org/logstash/config/ir/EventConditionTest.class' + include '/org/logstash/config/ir/PipelineConfigTest.class' + include '/org/logstash/config/ir/compiler/OutputDelegatorTest.class' + include '/org/logstash/config/ir/compiler/JavaCodecDelegatorTest.class' + include '/org/logstash/plugins/NamespacedMetricImplTest.class' + include '/org/logstash/plugins/CounterMetricImplTest.class' + include '/org/logstash/plugins/factory/PluginFactoryExtTest.class' + include '/org/logstash/execution/ObservedExecutionTest.class' +} + +// Setup task combining all prerequisites +tasks.register("testSetup") { + dependsOn ':bootstrap' + dependsOn 'classes' + dependsOn 'testClasses' + dependsOn 'compileTestJava' + dependsOn 'copyRuntimeLibs' + dependsOn 'generateVersionInfoResources' +} sourceSets { main { output.dir(generateVersionInfoResources.outputs.files) } } @@ -294,4 +283,4 @@ dependencies { api group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14' api group: 'commons-codec', name: 'commons-codec', version: '1.17.0' api group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.4.16' -} +} \ No newline at end of file diff --git a/rubyUtils.gradle b/rubyUtils.gradle index 94d020543c2..ab2281d377a 100644 --- a/rubyUtils.gradle +++ b/rubyUtils.gradle @@ -228,6 +228,7 @@ def customJRubyVersion = customJRubyDir == "" ? "" : Files.readAllLines(Paths.ge def customJRubyTar = customJRubyDir == "" ? "" : (customJRubyDir + "/maven/jruby-dist/target/jruby-dist-${customJRubyVersion}-bin.tar.gz") tasks.register("downloadJRuby", Download) { + onlyIf { !project.hasProperty('skipJRubySetup') } description "Download JRuby artifact from this specific URL: ${jRubyURL}" src jRubyURL onlyIfNewer true diff --git a/test-init.gradle b/test-init.gradle deleted file mode 100644 index 98304712769..00000000000 --- a/test-init.gradle +++ /dev/null @@ -1,47 +0,0 @@ -// test-init.gradle -initscript { - repositories { - mavenCentral() - } - dependencies { - classpath files( - '/root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bc-fips/2.0.0/ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab/bc-fips-2.0.0.jar', - '/root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-fips/2.0.7/1eea0f325315ca6295b0a6926ff862d8001cdf9/bcpkix-fips-2.0.7.jar', - '/root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bctls-fips/2.0.19/9cc33650ede63bc1a8281ed5c8e1da314d50bc76/bctls-fips-2.0.19.jar', - '/root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcutil-fips/2.0.3/a1857cd639295b10cc90e6d31ecbc523cdafcc19/bcutil-fips-2.0.3.jar' - ) - } -} - -gradle.taskGraph.whenReady { graph -> - if (graph.hasTask(":logstash-core:test") || - graph.hasTask(":logstash-core:rubyTests") || - graph.hasTask(":logstash-core:integrationTests")) { - - println "Registering BC FIPS provider for test execution..." - def provider = Class.forName('org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider').getDeclaredConstructor().newInstance() - java.security.Security.insertProviderAt(provider, 1) - - System.setProperty("javax.net.ssl.trustStore", "/etc/java/security/cacerts.bcfks") - System.setProperty("javax.net.ssl.trustStoreType", "BCFKS") - System.setProperty("javax.net.ssl.trustStoreProvider", "BCFIPS") - System.setProperty("javax.net.ssl.trustStorePassword", "changeit") - - def configureTask = { task -> - task.systemProperty "java.security.properties", "/etc/java/security/java.security" - task.systemProperty "javax.net.ssl.keyStore", "/etc/java/security/keystore.bcfks" - task.systemProperty "javax.net.ssl.keyStoreType", "BCFKS" - task.systemProperty "javax.net.ssl.keyStoreProvider", "BCFIPS" - task.systemProperty "javax.net.ssl.keyStorePassword", "changeit" - task.systemProperty "javax.net.ssl.trustStore", "/etc/java/security/cacerts.bcfks" - task.systemProperty "javax.net.ssl.trustStoreType", "BCFKS" - task.systemProperty "javax.net.ssl.trustStoreProvider", "BCFIPS" - } - - graph.allTasks.each { task -> - if (task.path in [":logstash-core:test", ":logstash-core:rubyTests", ":logstash-core:integrationTests"]) { - configureTask(task) - } - } - } -} \ No newline at end of file