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