001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.tool.coprocessor; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022import static org.mockito.Mockito.doReturn; 023import static org.mockito.Mockito.mock; 024 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.nio.file.Files; 029import java.nio.file.Path; 030import java.nio.file.Paths; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.Optional; 034import java.util.jar.JarOutputStream; 035import java.util.regex.Pattern; 036import java.util.zip.ZipEntry; 037import org.apache.hadoop.hbase.HBaseConfiguration; 038import org.apache.hadoop.hbase.TableName; 039import org.apache.hadoop.hbase.client.Admin; 040import org.apache.hadoop.hbase.client.CoprocessorDescriptor; 041import org.apache.hadoop.hbase.client.TableDescriptor; 042import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 043import org.apache.hadoop.hbase.coprocessor.ObserverContext; 044import org.apache.hadoop.hbase.testclassification.SmallTests; 045import org.apache.hadoop.hbase.tool.coprocessor.CoprocessorViolation.Severity; 046import org.junit.jupiter.api.Tag; 047import org.junit.jupiter.api.Test; 048 049import org.apache.hbase.thirdparty.com.google.common.base.Throwables; 050import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 051import org.apache.hbase.thirdparty.com.google.common.io.ByteStreams; 052 053@Tag(SmallTests.TAG) 054public class CoprocessorValidatorTest { 055 private CoprocessorValidator validator; 056 057 public CoprocessorValidatorTest() { 058 validator = new CoprocessorValidator(); 059 validator.setConf(HBaseConfiguration.create()); 060 } 061 062 private static ClassLoader getClassLoader() { 063 return CoprocessorValidatorTest.class.getClassLoader(); 064 } 065 066 private static String getFullClassName(String className) { 067 return CoprocessorValidatorTest.class.getName() + "$" + className; 068 } 069 070 private List<CoprocessorViolation> validateClass(String className) { 071 ClassLoader classLoader = getClass().getClassLoader(); 072 return validateClass(classLoader, className); 073 } 074 075 private List<CoprocessorViolation> validateClass(ClassLoader classLoader, String className) { 076 List<String> classNames = Lists.newArrayList(getFullClassName(className)); 077 List<CoprocessorViolation> violations = new ArrayList<>(); 078 079 validator.validateClasses(classLoader, classNames, violations); 080 081 return violations; 082 } 083 084 /* 085 * In this test case, we are try to load a not-existent class. 086 */ 087 @Test 088 public void testNoSuchClass() throws IOException { 089 List<CoprocessorViolation> violations = validateClass("NoSuchClass"); 090 assertEquals(1, violations.size()); 091 092 CoprocessorViolation violation = violations.get(0); 093 assertEquals(getFullClassName("NoSuchClass"), violation.getClassName()); 094 assertEquals(Severity.ERROR, violation.getSeverity()); 095 096 String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable()); 097 assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: " 098 + "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$NoSuchClass")); 099 } 100 101 /* 102 * In this test case, we are validating MissingClass coprocessor, which references a missing 103 * class. With a special classloader, we prevent that class to be loaded at runtime. It simulates 104 * similar cases where a class is no more on our classpath. E.g. 105 * org.apache.hadoop.hbase.regionserver.wal.WALEdit was moved to org.apache.hadoop.hbase.wal, so 106 * class loading will fail on 2.0. 107 */ 108 private static class MissingClass { 109 } 110 111 @SuppressWarnings("unused") 112 private static class MissingClassObserver { 113 public void method(MissingClass missingClass) { 114 } 115 } 116 117 private static class MissingClassClassLoader extends ClassLoader { 118 public MissingClassClassLoader() { 119 super(getClassLoader()); 120 } 121 122 @Override 123 public Class<?> loadClass(String name) throws ClassNotFoundException { 124 if (name.equals(getFullClassName("MissingClass"))) { 125 throw new ClassNotFoundException(name); 126 } 127 128 return super.findClass(name); 129 } 130 } 131 132 @Test 133 public void testMissingClass() throws IOException { 134 MissingClassClassLoader missingClassClassLoader = new MissingClassClassLoader(); 135 List<CoprocessorViolation> violations = 136 validateClass(missingClassClassLoader, "MissingClassObserver"); 137 assertEquals(1, violations.size()); 138 139 CoprocessorViolation violation = violations.get(0); 140 assertEquals(getFullClassName("MissingClassObserver"), violation.getClassName()); 141 assertEquals(Severity.ERROR, violation.getSeverity()); 142 143 String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable()); 144 assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: " 145 + "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$MissingClass")); 146 } 147 148 /** 149 * ObsoleteMethod coprocessor implements preCreateTable method which has HRegionInfo parameters. 150 * In our current implementation, we pass only RegionInfo parameters, so this method won't be 151 * called by HBase at all. 152 */ 153 @SuppressWarnings("unused") 154 private static class ObsoleteMethodObserver /* implements MasterObserver */ { 155 public void preEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx, 156 TableName tablName) throws IOException { 157 } 158 } 159 160 @Test 161 public void testObsoleteMethod() throws IOException { 162 List<CoprocessorViolation> violations = validateClass("ObsoleteMethodObserver"); 163 assertEquals(1, violations.size()); 164 165 CoprocessorViolation violation = violations.get(0); 166 assertEquals(Severity.WARNING, violation.getSeverity()); 167 assertEquals(getFullClassName("ObsoleteMethodObserver"), violation.getClassName()); 168 assertTrue(violation.getMessage().contains("was removed from new coprocessor API")); 169 } 170 171 private List<CoprocessorViolation> validateTable(String jarFile, String className) 172 throws IOException { 173 Pattern pattern = Pattern.compile(".*"); 174 175 Admin admin = mock(Admin.class); 176 177 TableDescriptor tableDescriptor = mock(TableDescriptor.class); 178 List<TableDescriptor> tableDescriptors = Lists.newArrayList(tableDescriptor); 179 doReturn(tableDescriptors).when(admin).listTableDescriptors(pattern); 180 181 CoprocessorDescriptor coprocessorDescriptor = mock(CoprocessorDescriptor.class); 182 List<CoprocessorDescriptor> coprocessorDescriptors = Lists.newArrayList(coprocessorDescriptor); 183 doReturn(coprocessorDescriptors).when(tableDescriptor).getCoprocessorDescriptors(); 184 185 doReturn(getFullClassName(className)).when(coprocessorDescriptor).getClassName(); 186 doReturn(Optional.ofNullable(jarFile)).when(coprocessorDescriptor).getJarPath(); 187 188 List<CoprocessorViolation> violations = new ArrayList<>(); 189 190 validator.validateTables(getClassLoader(), admin, pattern, violations); 191 192 return violations; 193 } 194 195 @Test 196 public void testTableNoSuchClass() throws IOException { 197 List<CoprocessorViolation> violations = validateTable(null, "NoSuchClass"); 198 assertEquals(1, violations.size()); 199 200 CoprocessorViolation violation = violations.get(0); 201 assertEquals(getFullClassName("NoSuchClass"), violation.getClassName()); 202 assertEquals(Severity.ERROR, violation.getSeverity()); 203 204 String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable()); 205 assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: " 206 + "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$NoSuchClass")); 207 } 208 209 @Test 210 public void testTableMissingJar() throws IOException { 211 List<CoprocessorViolation> violations = validateTable("no such file", "NoSuchClass"); 212 assertEquals(1, violations.size()); 213 214 CoprocessorViolation violation = violations.get(0); 215 assertEquals(getFullClassName("NoSuchClass"), violation.getClassName()); 216 assertEquals(Severity.ERROR, violation.getSeverity()); 217 assertTrue(violation.getMessage().contains("could not validate jar file 'no such file'")); 218 } 219 220 @Test 221 public void testTableValidJar() throws IOException { 222 Path outputDirectory = Paths.get("target", "test-classes"); 223 String className = getFullClassName("ObsoleteMethodObserver"); 224 Path classFile = Paths.get(className.replace('.', '/') + ".class"); 225 Path fullClassFile = outputDirectory.resolve(classFile); 226 227 Path tempJarFile = Files.createTempFile("coprocessor-validator-test-", ".jar"); 228 229 try { 230 try (OutputStream fileStream = Files.newOutputStream(tempJarFile); 231 JarOutputStream jarStream = new JarOutputStream(fileStream); 232 InputStream classStream = Files.newInputStream(fullClassFile)) { 233 ZipEntry entry = new ZipEntry(classFile.toString()); 234 jarStream.putNextEntry(entry); 235 236 ByteStreams.copy(classStream, jarStream); 237 } 238 239 String tempJarFileUri = tempJarFile.toUri().toString(); 240 241 List<CoprocessorViolation> violations = 242 validateTable(tempJarFileUri, "ObsoleteMethodObserver"); 243 assertEquals(1, violations.size()); 244 245 CoprocessorViolation violation = violations.get(0); 246 assertEquals(getFullClassName("ObsoleteMethodObserver"), violation.getClassName()); 247 assertEquals(Severity.WARNING, violation.getSeverity()); 248 assertTrue(violation.getMessage().contains("was removed from new coprocessor API")); 249 } finally { 250 Files.delete(tempJarFile); 251 } 252 } 253}