blob: 807f4fb7a62b905f8c1cfb897ec4d56584781601 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.tool.coprocessor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.jar.JarOutputStream;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.CoprocessorDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.tool.coprocessor.CoprocessorViolation.Severity;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
import org.apache.hbase.thirdparty.com.google.common.io.ByteStreams;
@Category({ SmallTests.class })
public class CoprocessorValidatorTest {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(CoprocessorValidatorTest.class);
private CoprocessorValidator validator;
public CoprocessorValidatorTest() {
validator = new CoprocessorValidator();
validator.setConf(HBaseConfiguration.create());
}
private static ClassLoader getClassLoader() {
return CoprocessorValidatorTest.class.getClassLoader();
}
private static String getFullClassName(String className) {
return CoprocessorValidatorTest.class.getName() + "$" + className;
}
private List<CoprocessorViolation> validateClass(String className) {
ClassLoader classLoader = getClass().getClassLoader();
return validateClass(classLoader, className);
}
private List<CoprocessorViolation> validateClass(ClassLoader classLoader, String className) {
List<String> classNames = Lists.newArrayList(getFullClassName(className));
List<CoprocessorViolation> violations = new ArrayList<>();
validator.validateClasses(classLoader, classNames, violations);
return violations;
}
/*
* In this test case, we are try to load a not-existent class.
*/
@Test
public void testNoSuchClass() throws IOException {
List<CoprocessorViolation> violations = validateClass("NoSuchClass");
assertEquals(1, violations.size());
CoprocessorViolation violation = violations.get(0);
assertEquals(getFullClassName("NoSuchClass"), violation.getClassName());
assertEquals(Severity.ERROR, violation.getSeverity());
String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable());
assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: "
+ "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$NoSuchClass"));
}
/*
* In this test case, we are validating MissingClass coprocessor, which references a missing
* class. With a special classloader, we prevent that class to be loaded at runtime. It simulates
* similar cases where a class is no more on our classpath. E.g.
* org.apache.hadoop.hbase.regionserver.wal.WALEdit was moved to org.apache.hadoop.hbase.wal, so
* class loading will fail on 2.0.
*/
private static class MissingClass {
}
@SuppressWarnings("unused")
private static class MissingClassObserver {
public void method(MissingClass missingClass) {
}
}
private static class MissingClassClassLoader extends ClassLoader {
public MissingClassClassLoader() {
super(getClassLoader());
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.equals(getFullClassName("MissingClass"))) {
throw new ClassNotFoundException(name);
}
return super.findClass(name);
}
}
@Test
public void testMissingClass() throws IOException {
MissingClassClassLoader missingClassClassLoader = new MissingClassClassLoader();
List<CoprocessorViolation> violations =
validateClass(missingClassClassLoader, "MissingClassObserver");
assertEquals(1, violations.size());
CoprocessorViolation violation = violations.get(0);
assertEquals(getFullClassName("MissingClassObserver"), violation.getClassName());
assertEquals(Severity.ERROR, violation.getSeverity());
String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable());
assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: "
+ "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$MissingClass"));
}
/**
* ObsoleteMethod coprocessor implements preCreateTable method which has HRegionInfo parameters.
* In our current implementation, we pass only RegionInfo parameters, so this method won't be
* called by HBase at all.
*/
@SuppressWarnings("unused")
private static class ObsoleteMethodObserver /* implements MasterObserver */ {
public void preEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
TableName tablName) throws IOException {
}
}
@Test
public void testObsoleteMethod() throws IOException {
List<CoprocessorViolation> violations = validateClass("ObsoleteMethodObserver");
assertEquals(1, violations.size());
CoprocessorViolation violation = violations.get(0);
assertEquals(Severity.WARNING, violation.getSeverity());
assertEquals(getFullClassName("ObsoleteMethodObserver"), violation.getClassName());
assertTrue(violation.getMessage().contains("was removed from new coprocessor API"));
}
private List<CoprocessorViolation> validateTable(String jarFile, String className)
throws IOException {
Pattern pattern = Pattern.compile(".*");
Admin admin = mock(Admin.class);
TableDescriptor tableDescriptor = mock(TableDescriptor.class);
List<TableDescriptor> tableDescriptors = Lists.newArrayList(tableDescriptor);
doReturn(tableDescriptors).when(admin).listTableDescriptors(pattern);
CoprocessorDescriptor coprocessorDescriptor = mock(CoprocessorDescriptor.class);
List<CoprocessorDescriptor> coprocessorDescriptors = Lists.newArrayList(coprocessorDescriptor);
doReturn(coprocessorDescriptors).when(tableDescriptor).getCoprocessorDescriptors();
doReturn(getFullClassName(className)).when(coprocessorDescriptor).getClassName();
doReturn(Optional.ofNullable(jarFile)).when(coprocessorDescriptor).getJarPath();
List<CoprocessorViolation> violations = new ArrayList<>();
validator.validateTables(getClassLoader(), admin, pattern, violations);
return violations;
}
@Test
public void testTableNoSuchClass() throws IOException {
List<CoprocessorViolation> violations = validateTable(null, "NoSuchClass");
assertEquals(1, violations.size());
CoprocessorViolation violation = violations.get(0);
assertEquals(getFullClassName("NoSuchClass"), violation.getClassName());
assertEquals(Severity.ERROR, violation.getSeverity());
String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable());
assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: "
+ "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$NoSuchClass"));
}
@Test
public void testTableMissingJar() throws IOException {
List<CoprocessorViolation> violations = validateTable("no such file", "NoSuchClass");
assertEquals(1, violations.size());
CoprocessorViolation violation = violations.get(0);
assertEquals(getFullClassName("NoSuchClass"), violation.getClassName());
assertEquals(Severity.ERROR, violation.getSeverity());
assertTrue(violation.getMessage().contains("could not validate jar file 'no such file'"));
}
@Test
public void testTableValidJar() throws IOException {
Path outputDirectory = Paths.get("target", "test-classes");
String className = getFullClassName("ObsoleteMethodObserver");
Path classFile = Paths.get(className.replace('.', '/') + ".class");
Path fullClassFile = outputDirectory.resolve(classFile);
Path tempJarFile = Files.createTempFile("coprocessor-validator-test-", ".jar");
try {
try (OutputStream fileStream = Files.newOutputStream(tempJarFile);
JarOutputStream jarStream = new JarOutputStream(fileStream);
InputStream classStream = Files.newInputStream(fullClassFile)) {
ZipEntry entry = new ZipEntry(classFile.toString());
jarStream.putNextEntry(entry);
ByteStreams.copy(classStream, jarStream);
}
String tempJarFileUri = tempJarFile.toUri().toString();
List<CoprocessorViolation> violations =
validateTable(tempJarFileUri, "ObsoleteMethodObserver");
assertEquals(1, violations.size());
CoprocessorViolation violation = violations.get(0);
assertEquals(getFullClassName("ObsoleteMethodObserver"), violation.getClassName());
assertEquals(Severity.WARNING, violation.getSeverity());
assertTrue(violation.getMessage().contains("was removed from new coprocessor API"));
} finally {
Files.delete(tempJarFile);
}
}
}