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