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.coprocessor;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertThrows;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.File;
026import java.lang.reflect.InvocationTargetException;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.hbase.Abortable;
029import org.apache.hadoop.hbase.Coprocessor;
030import org.apache.hadoop.hbase.CoprocessorEnvironment;
031import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
032import org.apache.hadoop.hbase.HBaseConfiguration;
033import org.apache.hadoop.hbase.testclassification.SmallTests;
034import org.apache.hadoop.hbase.util.ClassLoaderTestHelper;
035import org.junit.jupiter.api.Tag;
036import org.junit.jupiter.api.Test;
037
038@Tag(SmallTests.TAG)
039public class TestCoprocessorHost {
040
041  private static final HBaseCommonTestingUtil TEST_UTIL = new HBaseCommonTestingUtil();
042
043  /**
044   * An {@link Abortable} implementation for tests.
045   */
046  private static class TestAbortable implements Abortable {
047    private volatile boolean aborted = false;
048
049    @Override
050    public void abort(String why, Throwable e) {
051      this.aborted = true;
052      fail(e.getMessage());
053    }
054
055    @Override
056    public boolean isAborted() {
057      return this.aborted;
058    }
059  }
060
061  @Test
062  public void testDoubleLoadingAndPriorityValue() {
063    final Configuration conf = HBaseConfiguration.create();
064    final String key = "KEY";
065    final String coprocessor = "org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver";
066
067    CoprocessorHost<RegionCoprocessor, CoprocessorEnvironment<RegionCoprocessor>> host;
068    host = new CoprocessorHostForTest<>(conf);
069    int overridePriority = Integer.MAX_VALUE - 1;
070
071    final String coprocessor_v3 = SimpleRegionObserverV3.class.getName() + "|" + overridePriority;
072
073    // Try and load a coprocessor three times
074    conf.setStrings(key, coprocessor, coprocessor, coprocessor,
075      SimpleRegionObserverV2.class.getName(), coprocessor_v3);
076    host.loadSystemCoprocessors(conf, key);
077
078    // Three coprocessors(SimpleRegionObserver, SimpleRegionObserverV2,
079    // SimpleRegionObserverV3) loaded
080    assertEquals(3, host.coprocEnvironments.size());
081
082    // Check the priority value
083    CoprocessorEnvironment<?> simpleEnv =
084      host.findCoprocessorEnvironment(SimpleRegionObserver.class.getName());
085    CoprocessorEnvironment<?> simpleEnv_v2 =
086      host.findCoprocessorEnvironment(SimpleRegionObserverV2.class.getName());
087    CoprocessorEnvironment<?> simpleEnv_v3 =
088      host.findCoprocessorEnvironment(SimpleRegionObserverV3.class.getName());
089
090    assertNotNull(simpleEnv);
091    assertNotNull(simpleEnv_v2);
092    assertNotNull(simpleEnv_v3);
093    assertEquals(Coprocessor.PRIORITY_SYSTEM, simpleEnv.getPriority());
094    assertEquals(Coprocessor.PRIORITY_SYSTEM + 1, simpleEnv_v2.getPriority());
095    assertEquals(overridePriority, simpleEnv_v3.getPriority());
096  }
097
098  @Test
099  public void testLoadSystemCoprocessorWithPath() throws Exception {
100    Configuration conf = TEST_UTIL.getConfiguration();
101    final String key = "KEY";
102    final String testClassName = "TestSystemCoprocessor";
103    final String testClassNameWithPriorityAndPath = testClassName + "PriorityAndPath";
104
105    File jarFile = buildCoprocessorJar(testClassName);
106    File jarFileWithPriorityAndPath = buildCoprocessorJar(testClassNameWithPriorityAndPath);
107
108    try {
109      CoprocessorHost<RegionCoprocessor, CoprocessorEnvironment<RegionCoprocessor>> host;
110      host = new CoprocessorHostForTest<>(conf);
111
112      // make a string of coprocessor with only priority
113      int overridePriority = Integer.MAX_VALUE - 1;
114      final String coprocessorWithPriority =
115        SimpleRegionObserverV3.class.getName() + "|" + overridePriority;
116      // make a string of coprocessor with path but no priority
117      final String coprocessorWithPath =
118        String.format("%s|%s|%s", testClassName, "", jarFile.getAbsolutePath());
119      // make a string of coprocessor with priority and path
120      final String coprocessorWithPriorityAndPath =
121        String.format("%s|%s|%s", testClassNameWithPriorityAndPath, (overridePriority - 1),
122          jarFileWithPriorityAndPath.getAbsolutePath());
123
124      // Try and load a system coprocessors
125      conf.setStrings(key, SimpleRegionObserverV2.class.getName(), coprocessorWithPriority,
126        coprocessorWithPath, coprocessorWithPriorityAndPath);
127      host.loadSystemCoprocessors(conf, key);
128
129      // first loaded system coprocessor with default priority
130      CoprocessorEnvironment<?> simpleEnv =
131        host.findCoprocessorEnvironment(SimpleRegionObserverV2.class.getName());
132      assertNotNull(simpleEnv);
133      assertEquals(Coprocessor.PRIORITY_SYSTEM, simpleEnv.getPriority());
134
135      // external system coprocessor with default priority
136      CoprocessorEnvironment<?> coprocessorEnvironmentWithPath =
137        host.findCoprocessorEnvironment(testClassName);
138      assertNotNull(coprocessorEnvironmentWithPath);
139      assertEquals(Coprocessor.PRIORITY_SYSTEM + 1, coprocessorEnvironmentWithPath.getPriority());
140
141      // system coprocessor with configured priority
142      CoprocessorEnvironment<?> coprocessorEnvironmentWithPriority =
143        host.findCoprocessorEnvironment(SimpleRegionObserverV3.class.getName());
144      assertNotNull(coprocessorEnvironmentWithPriority);
145      assertEquals(overridePriority, coprocessorEnvironmentWithPriority.getPriority());
146
147      // external system coprocessor with override priority
148      CoprocessorEnvironment<?> coprocessorEnvironmentWithPriorityAndPath =
149        host.findCoprocessorEnvironment(testClassNameWithPriorityAndPath);
150      assertNotNull(coprocessorEnvironmentWithPriorityAndPath);
151      assertEquals(overridePriority - 1, coprocessorEnvironmentWithPriorityAndPath.getPriority());
152    } finally {
153      if (jarFile.exists()) {
154        jarFile.delete();
155      }
156      if (jarFileWithPriorityAndPath.exists()) {
157        jarFileWithPriorityAndPath.delete();
158      }
159    }
160  }
161
162  @Test
163  public void testLoadSystemCoprocessorWithPathDoesNotExist() throws Exception {
164    Configuration conf = TEST_UTIL.getConfiguration();
165    final String key = "KEY";
166    final String testClassName = "TestSystemCoprocessor";
167
168    CoprocessorHost<RegionCoprocessor, CoprocessorEnvironment<RegionCoprocessor>> host;
169    host = new CoprocessorHostForTest<>(conf);
170
171    // make a string of coprocessor with path but no priority
172    final String coprocessorWithPath = testClassName + "||" + testClassName + ".jar";
173
174    // Try and load a system coprocessors
175    conf.setStrings(key, coprocessorWithPath);
176    // when loading non-exist with CoprocessorHostForTest host, it aborts with AssertionError
177    assertThrows(AssertionError.class, () -> host.loadSystemCoprocessors(conf, key));
178  }
179
180  @Test
181  public void testLoadSystemCoprocessorWithPathDoesNotExistAndPriority() throws Exception {
182    Configuration conf = TEST_UTIL.getConfiguration();
183    final String key = "KEY";
184    final String testClassName = "TestSystemCoprocessor";
185
186    CoprocessorHost<RegionCoprocessor, CoprocessorEnvironment<RegionCoprocessor>> host;
187    host = new CoprocessorHostForTest<>(conf);
188
189    int overridePriority = Integer.MAX_VALUE - 1;
190    // make a string of coprocessor with path and priority
191    final String coprocessor =
192      testClassName + "|" + overridePriority + "|" + testClassName + ".jar";
193
194    // Try and load a system coprocessors
195    conf.setStrings(key, coprocessor);
196    // when loading non-exist coprocessor, it aborts with AssertionError
197    assertThrows(AssertionError.class, () -> host.loadSystemCoprocessors(conf, key));
198  }
199
200  public static class SimpleRegionObserverV2 extends SimpleRegionObserver {
201  }
202
203  public static class SimpleRegionObserverV3 extends SimpleRegionObserver {
204
205  }
206
207  private static class CoprocessorHostForTest<E extends Coprocessor>
208    extends CoprocessorHost<E, CoprocessorEnvironment<E>> {
209    final Configuration cpHostConf;
210
211    public CoprocessorHostForTest(Configuration conf) {
212      super(new TestAbortable());
213      cpHostConf = conf;
214    }
215
216    @Override
217    public E checkAndGetInstance(Class<?> implClass)
218      throws InstantiationException, IllegalAccessException {
219      try {
220        return (E) implClass.getDeclaredConstructor().newInstance();
221      } catch (InvocationTargetException | NoSuchMethodException e) {
222        throw (InstantiationException) new InstantiationException().initCause(e);
223      }
224    }
225
226    @Override
227    public CoprocessorEnvironment<E> createEnvironment(final E instance, final int priority,
228      int sequence, Configuration conf) {
229      return new BaseEnvironment<>(instance, priority, 0, cpHostConf);
230    }
231  }
232
233  private File buildCoprocessorJar(String className) throws Exception {
234    String dataTestDir = TEST_UTIL.getDataTestDir().toString();
235    String code = String.format("import org.apache.hadoop.hbase.coprocessor.*; public class %s"
236      + " implements RegionCoprocessor {}", className);
237    return ClassLoaderTestHelper.buildJar(dataTestDir, className, code);
238  }
239}