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 */ 018 019package org.apache.hadoop.hbase.tool.coprocessor; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertTrue; 023import static org.mockito.Mockito.doReturn; 024import static org.mockito.Mockito.mock; 025 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.OutputStream; 029import java.nio.file.Files; 030import java.nio.file.Path; 031import java.nio.file.Paths; 032import java.util.ArrayList; 033import java.util.List; 034import java.util.Optional; 035import java.util.jar.JarOutputStream; 036import java.util.regex.Pattern; 037import java.util.zip.ZipEntry; 038 039import org.apache.hadoop.hbase.HBaseClassTestRule; 040import org.apache.hadoop.hbase.HBaseConfiguration; 041import org.apache.hadoop.hbase.HRegionInfo; 042import org.apache.hadoop.hbase.HTableDescriptor; 043import org.apache.hadoop.hbase.client.Admin; 044import org.apache.hadoop.hbase.client.CoprocessorDescriptor; 045import org.apache.hadoop.hbase.client.TableDescriptor; 046import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 047import org.apache.hadoop.hbase.coprocessor.ObserverContext; 048import org.apache.hadoop.hbase.testclassification.SmallTests; 049import org.apache.hadoop.hbase.tool.coprocessor.CoprocessorViolation.Severity; 050import org.junit.ClassRule; 051import org.junit.Test; 052import org.junit.experimental.categories.Category; 053 054import org.apache.hbase.thirdparty.com.google.common.base.Throwables; 055import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 056import org.apache.hbase.thirdparty.com.google.common.io.ByteStreams; 057 058@Category({ SmallTests.class }) 059@SuppressWarnings("deprecation") 060public class CoprocessorValidatorTest { 061 @ClassRule 062 public static final HBaseClassTestRule CLASS_RULE = 063 HBaseClassTestRule.forClass(CoprocessorValidatorTest.class); 064 065 private CoprocessorValidator validator; 066 067 public CoprocessorValidatorTest() { 068 validator = new CoprocessorValidator(); 069 validator.setConf(HBaseConfiguration.create()); 070 } 071 072 private static ClassLoader getClassLoader() { 073 return CoprocessorValidatorTest.class.getClassLoader(); 074 } 075 076 private static String getFullClassName(String className) { 077 return CoprocessorValidatorTest.class.getName() + "$" + className; 078 } 079 080 private List<CoprocessorViolation> validateClass(String className) { 081 ClassLoader classLoader = getClass().getClassLoader(); 082 return validateClass(classLoader, className); 083 } 084 085 private List<CoprocessorViolation> validateClass(ClassLoader classLoader, String className) { 086 List<String> classNames = Lists.newArrayList(getFullClassName(className)); 087 List<CoprocessorViolation> violations = new ArrayList<>(); 088 089 validator.validateClasses(classLoader, classNames, violations); 090 091 return violations; 092 } 093 094 /* 095 * In this test case, we are try to load a not-existent class. 096 */ 097 @Test 098 public void testNoSuchClass() throws IOException { 099 List<CoprocessorViolation> violations = validateClass("NoSuchClass"); 100 assertEquals(1, violations.size()); 101 102 CoprocessorViolation violation = violations.get(0); 103 assertEquals(getFullClassName("NoSuchClass"), violation.getClassName()); 104 assertEquals(Severity.ERROR, violation.getSeverity()); 105 106 String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable()); 107 assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: " + 108 "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$NoSuchClass")); 109 } 110 111 /* 112 * In this test case, we are validating MissingClass coprocessor, which 113 * references a missing class. With a special classloader, we prevent that 114 * class to be loaded at runtime. It simulates similar cases where a class 115 * is no more on our classpath. 116 * E.g. org.apache.hadoop.hbase.regionserver.wal.WALEdit was moved to 117 * org.apache.hadoop.hbase.wal, so class loading will fail on 2.0. 118 */ 119 private static class MissingClass { 120 } 121 122 @SuppressWarnings("unused") 123 private static class MissingClassObserver { 124 public void method(MissingClass missingClass) { 125 } 126 } 127 128 private static class MissingClassClassLoader extends ClassLoader { 129 public MissingClassClassLoader() { 130 super(getClassLoader()); 131 } 132 133 @Override 134 public Class<?> loadClass(String name) throws ClassNotFoundException { 135 if (name.equals(getFullClassName("MissingClass"))) { 136 throw new ClassNotFoundException(name); 137 } 138 139 return super.findClass(name); 140 } 141 } 142 143 @Test 144 public void testMissingClass() throws IOException { 145 MissingClassClassLoader missingClassClassLoader = new MissingClassClassLoader(); 146 List<CoprocessorViolation> violations = validateClass(missingClassClassLoader, 147 "MissingClassObserver"); 148 assertEquals(1, violations.size()); 149 150 CoprocessorViolation violation = violations.get(0); 151 assertEquals(getFullClassName("MissingClassObserver"), violation.getClassName()); 152 assertEquals(Severity.ERROR, violation.getSeverity()); 153 154 String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable()); 155 assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: " + 156 "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$MissingClass")); 157 } 158 159 /* 160 * ObsoleteMethod coprocessor implements preCreateTable method which has 161 * HRegionInfo parameters. In our current implementation, we pass only 162 * RegionInfo parameters, so this method won't be called by HBase at all. 163 */ 164 @SuppressWarnings("unused") 165 private static class ObsoleteMethodObserver /* implements MasterObserver */ { 166 public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx, 167 HTableDescriptor desc, HRegionInfo[] regions) throws IOException { 168 } 169 } 170 171 @Test 172 public void testObsoleteMethod() throws IOException { 173 List<CoprocessorViolation> violations = validateClass("ObsoleteMethodObserver"); 174 assertEquals(1, violations.size()); 175 176 CoprocessorViolation violation = violations.get(0); 177 assertEquals(Severity.WARNING, violation.getSeverity()); 178 assertEquals(getFullClassName("ObsoleteMethodObserver"), violation.getClassName()); 179 assertTrue(violation.getMessage().contains("was removed from new coprocessor API")); 180 } 181 182 private List<CoprocessorViolation> validateTable(String jarFile, String className) 183 throws IOException { 184 Pattern pattern = Pattern.compile(".*"); 185 186 Admin admin = mock(Admin.class); 187 188 TableDescriptor tableDescriptor = mock(TableDescriptor.class); 189 List<TableDescriptor> tableDescriptors = Lists.newArrayList(tableDescriptor); 190 doReturn(tableDescriptors).when(admin).listTableDescriptors(pattern); 191 192 CoprocessorDescriptor coprocessorDescriptor = mock(CoprocessorDescriptor.class); 193 List<CoprocessorDescriptor> coprocessorDescriptors = 194 Lists.newArrayList(coprocessorDescriptor); 195 doReturn(coprocessorDescriptors).when(tableDescriptor).getCoprocessorDescriptors(); 196 197 doReturn(getFullClassName(className)).when(coprocessorDescriptor).getClassName(); 198 doReturn(Optional.ofNullable(jarFile)).when(coprocessorDescriptor).getJarPath(); 199 200 List<CoprocessorViolation> violations = new ArrayList<>(); 201 202 validator.validateTables(getClassLoader(), admin, pattern, violations); 203 204 return violations; 205 } 206 207 @Test 208 public void testTableNoSuchClass() throws IOException { 209 List<CoprocessorViolation> violations = validateTable(null, "NoSuchClass"); 210 assertEquals(1, violations.size()); 211 212 CoprocessorViolation violation = violations.get(0); 213 assertEquals(getFullClassName("NoSuchClass"), violation.getClassName()); 214 assertEquals(Severity.ERROR, violation.getSeverity()); 215 216 String stackTrace = Throwables.getStackTraceAsString(violation.getThrowable()); 217 assertTrue(stackTrace.contains("java.lang.ClassNotFoundException: " + 218 "org.apache.hadoop.hbase.tool.coprocessor.CoprocessorValidatorTest$NoSuchClass")); 219 } 220 221 @Test 222 public void testTableMissingJar() throws IOException { 223 List<CoprocessorViolation> violations = validateTable("no such file", "NoSuchClass"); 224 assertEquals(1, violations.size()); 225 226 CoprocessorViolation violation = violations.get(0); 227 assertEquals(getFullClassName("NoSuchClass"), violation.getClassName()); 228 assertEquals(Severity.ERROR, violation.getSeverity()); 229 assertTrue(violation.getMessage().contains("could not validate jar file 'no such file'")); 230 } 231 232 @Test 233 public void testTableValidJar() throws IOException { 234 Path outputDirectory = Paths.get("target", "test-classes"); 235 String className = getFullClassName("ObsoleteMethodObserver"); 236 Path classFile = Paths.get(className.replace('.', '/') + ".class"); 237 Path fullClassFile = outputDirectory.resolve(classFile); 238 239 Path tempJarFile = Files.createTempFile("coprocessor-validator-test-", ".jar"); 240 241 try { 242 try (OutputStream fileStream = Files.newOutputStream(tempJarFile); 243 JarOutputStream jarStream = new JarOutputStream(fileStream); 244 InputStream classStream = Files.newInputStream(fullClassFile)) { 245 ZipEntry entry = new ZipEntry(classFile.toString()); 246 jarStream.putNextEntry(entry); 247 248 ByteStreams.copy(classStream, jarStream); 249 } 250 251 String tempJarFileUri = tempJarFile.toUri().toString(); 252 253 List<CoprocessorViolation> violations = 254 validateTable(tempJarFileUri, "ObsoleteMethodObserver"); 255 assertEquals(1, violations.size()); 256 257 CoprocessorViolation violation = violations.get(0); 258 assertEquals(getFullClassName("ObsoleteMethodObserver"), violation.getClassName()); 259 assertEquals(Severity.WARNING, violation.getSeverity()); 260 assertTrue(violation.getMessage().contains("was removed from new coprocessor API")); 261 } finally { 262 Files.delete(tempJarFile); 263 } 264 } 265}