Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;

import org.gradle.api.artifacts.transform.CacheableTransform;
import org.gradle.api.artifacts.transform.InputArtifact;
import org.gradle.api.artifacts.transform.TransformAction;
import org.gradle.api.artifacts.transform.TransformOutputs;
import org.gradle.api.artifacts.transform.TransformParameters;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.stream.Stream;

import static java.util.Map.entry;

@CacheableTransform
public abstract class HdfsClassPatcher implements TransformAction<HdfsClassPatcher.Parameters> {

record JarPatchers(String artifactName, Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers) {}

static final List<JarPatchers> allPatchers = List.of(
new JarPatchers(
"hadoop-common",
Map.ofEntries(
entry("org/apache/hadoop/util/ShutdownHookManager.class", ShutdownHookManagerPatcher::new),
entry("org/apache/hadoop/util/Shell.class", ShellPatcher::new),
entry("org/apache/hadoop/security/UserGroupInformation.class", SubjectGetSubjectPatcher::new)
)
),
new JarPatchers(
"hadoop-client-api",
Map.ofEntries(
entry("org/apache/hadoop/util/ShutdownHookManager.class", ShutdownHookManagerPatcher::new),
entry("org/apache/hadoop/util/Shell.class", ShellPatcher::new),
entry("org/apache/hadoop/security/UserGroupInformation.class", SubjectGetSubjectPatcher::new),
entry("org/apache/hadoop/security/authentication/client/KerberosAuthenticator.class", SubjectGetSubjectPatcher::new)
)
)
);

interface Parameters extends TransformParameters {
@Input
@Optional
List<String> getMatchingArtifacts();

void setMatchingArtifacts(List<String> matchingArtifacts);
}

@Classpath
@InputArtifact
public abstract Provider<FileSystemLocation> getInputArtifact();

@Override
public void transform(@NotNull TransformOutputs outputs) {
File inputFile = getInputArtifact().get().getAsFile();

List<String> matchingArtifacts = getParameters().getMatchingArtifacts();
if (matchingArtifacts.isEmpty() == false
&& matchingArtifacts.stream().noneMatch(supported -> inputFile.getName().contains(supported))) {
outputs.file(getInputArtifact());
} else {
Stream<JarPatchers> patchersToApply = allPatchers.stream().filter(jp -> matchingArtifacts.contains(jp.artifactName()));
patchersToApply.forEach(patchers -> {
File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar"));
patchJar(inputFile, outputFile, patchers.jarPatchers());
});
}
}

private static void patchJar(File inputFile, File outputFile, Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers) {
try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
// Add the entry to the new JAR file
jos.putNextEntry(new JarEntry(entryName));

Function<ClassWriter, ClassVisitor> classPatcher = jarPatchers.get(entryName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add a check here to verify all expected patches are applied and fail early if not to avoid having undetected changes sneaked into this again by e.g. updating hadoop versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do that and it's a great idea; however there is one big blocker for this: the hadoop-common artifact has one additional "classifier" named tests, which contains only tests of course but is brought in anyway.
I tried to exclude it but apparently it's not possible to do that in gradle (there is limited support for classifiers).
The thing is that hadoop-common is applied to both hadoop-common jars (regular and "tests"), and it will fail on the second, because none of the expected patches will be applied there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: if you have a good way to either exclude hadoop-common:tests or to recognize it and avoid trying to patch it I'm all ears!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could tweak the regex to match the file name being stricter in a sense of expected name + version regex instead of just a simple string.contains based check to fix to not match -tests artifacts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that; I discarded it because "2.8.5-tests" is a valid "version" (isn't it? like 1.0.0-beta).
I can allow only the numeric part; it's not super clean but..
I would have preferred excluding the jar completely, since it's only tests (no purpose having it), but seems that this is very hard to do in gradle

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try and see the regex

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to deal with that test classifier stuff with hadoop before and suffered myself. okay. lets just live with that for now. we can revisit later if we feel the need. I will use parts of that stuff later on to fix other places in our codebase.

if (classPatcher != null) {
byte[] classToPatch = jarFile.getInputStream(entry).readAllBytes();

ClassReader classReader = new ClassReader(classToPatch);
ClassWriter classWriter = new ClassWriter(classReader, 0);
classReader.accept(classPatcher.apply(classWriter), 0);

jos.write(classWriter.toByteArray());
} else {
// Read the entry's data and write it to the new JAR
try (InputStream is = jarFile.getInputStream(entry)) {
is.transferTo(jos);
}
}
jos.closeEntry();
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.hdfs.patch;
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.hdfs.patch;
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.hdfs.patch;
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.hdfs.patch;
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
Expand Down
6 changes: 0 additions & 6 deletions muted-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,6 @@ tests:
- class: org.elasticsearch.analysis.common.CommonAnalysisClientYamlTestSuiteIT
method: test {yaml=analysis-common/40_token_filters/stemmer_override file access}
issue: https://github.com/elastic/elasticsearch/issues/121625
- class: org.elasticsearch.xpack.searchablesnapshots.hdfs.SecureHdfsSearchableSnapshotsIT
issue: https://github.com/elastic/elasticsearch/issues/121967
- class: org.elasticsearch.xpack.application.CohereServiceUpgradeIT
issue: https://github.com/elastic/elasticsearch/issues/121537
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
Expand Down Expand Up @@ -293,10 +291,6 @@ tests:
- class: org.elasticsearch.smoketest.DocsClientYamlTestSuiteIT
method: test {yaml=reference/snapshot-restore/apis/get-snapshot-api/line_488}
issue: https://github.com/elastic/elasticsearch/issues/121611
- class: org.elasticsearch.repositories.blobstore.testkit.analyze.SecureHdfsRepositoryAnalysisRestIT
issue: https://github.com/elastic/elasticsearch/issues/122377
- class: org.elasticsearch.repositories.blobstore.testkit.analyze.HdfsRepositoryAnalysisRestIT
issue: https://github.com/elastic/elasticsearch/issues/122378
- class: org.elasticsearch.xpack.inference.mapper.SemanticInferenceMetadataFieldsRecoveryTests
method: testSnapshotRecovery {p0=false p1=false}
issue: https://github.com/elastic/elasticsearch/issues/122549
Expand Down
56 changes: 47 additions & 9 deletions plugins/repository-hdfs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,35 @@ versions << [
'hadoop': '3.4.1'
]

def patched = Attribute.of('patched', Boolean)

configurations {
hdfsFixture2
hdfsFixture3
compileClasspath {
attributes {
attribute(patched, true)
}
}
runtimeClasspath {
attributes {
attribute(patched, true)
}
}
testCompileClasspath {
attributes {
attribute(patched, true)
}
}
testRuntimeClasspath {
attributes {
attribute(patched, true)
}
}
}

dependencies {
api project(path: 'hadoop-client-api', configuration: 'default')
if (isEclipse) {
/*
* Eclipse can't pick up the shadow dependency so we point it at *something*
* so it can compile things.
*/
api project(path: 'hadoop-client-api')
}
api("org.apache.hadoop:hadoop-client-api:${versions.hadoop}")
runtimeOnly "org.apache.hadoop:hadoop-client-runtime:${versions.hadoop}"
implementation "org.apache.hadoop:hadoop-hdfs:${versions.hadoop}"
api "com.google.protobuf:protobuf-java:${versions.protobuf}"
Expand Down Expand Up @@ -69,6 +84,20 @@ dependencies {

hdfsFixture2 project(path: ':test:fixtures:hdfs-fixture', configuration: 'shadowedHdfs2')
hdfsFixture3 project(path: ':test:fixtures:hdfs-fixture', configuration: 'shadow')

attributesSchema {
attribute(patched)
}
artifactTypes.getByName("jar") {
attributes.attribute(patched, false)
}
registerTransform(org.elasticsearch.gradle.internal.dependencies.patches.hdfs.HdfsClassPatcher) {
from.attribute(patched, false)
to.attribute(patched, true)
parameters {
matchingArtifacts = ["hadoop-client-api"]
}
}
}

restResources {
Expand Down Expand Up @@ -190,6 +219,15 @@ tasks.named("thirdPartyAudit").configure {
'org.apache.hadoop.thirdparty.protobuf.UnsafeUtil$MemoryAccessor',
'org.apache.hadoop.thirdparty.protobuf.MessageSchema',
'org.apache.hadoop.thirdparty.protobuf.UnsafeUtil$Android32MemoryAccessor',
'org.apache.hadoop.thirdparty.protobuf.UnsafeUtil$Android64MemoryAccessor'
'org.apache.hadoop.thirdparty.protobuf.UnsafeUtil$Android64MemoryAccessor',
'org.apache.hadoop.thirdparty.protobuf.UnsafeUtil$Android64MemoryAccessor',
'org.apache.hadoop.hdfs.shortcircuit.ShortCircuitShm',
'org.apache.hadoop.hdfs.shortcircuit.ShortCircuitShm$Slot',
'org.apache.hadoop.io.FastByteComparisons$LexicographicalComparerHolder$UnsafeComparer',
'org.apache.hadoop.io.FastByteComparisons$LexicographicalComparerHolder$UnsafeComparer$1',
'org.apache.hadoop.io.nativeio.NativeIO',
'org.apache.hadoop.service.launcher.InterruptEscalator',
'org.apache.hadoop.service.launcher.IrqHandler',
'org.apache.hadoop.util.SignalLogger$Handler'
)
}
54 changes: 0 additions & 54 deletions plugins/repository-hdfs/hadoop-client-api/build.gradle

This file was deleted.

Loading