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.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertThrows;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024import static org.mockito.Mockito.mock;
025import static org.mockito.Mockito.when;
026
027import java.io.IOException;
028import java.util.concurrent.atomic.AtomicBoolean;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.CoprocessorEnvironment;
031import org.apache.hadoop.hbase.HBaseConfiguration;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.client.RegionInfoBuilder;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
038import org.apache.hadoop.hbase.master.MasterServices;
039import org.apache.hadoop.hbase.regionserver.HRegion;
040import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
041import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
042import org.apache.hadoop.hbase.regionserver.RegionServerServices;
043import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
044import org.apache.hadoop.hbase.testclassification.SmallTests;
045import org.junit.jupiter.api.Tag;
046import org.junit.jupiter.api.Test;
047
048/**
049 * Tests for global coprocessor loading configuration
050 */
051@Tag(CoprocessorTests.TAG)
052@Tag(SmallTests.TAG)
053public class TestCoprocessorConfiguration {
054
055  private static final Configuration CONF = HBaseConfiguration.create();
056  static {
057    CONF.setStrings(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, SystemCoprocessor.class.getName());
058    CONF.setStrings(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
059      SystemCoprocessor.class.getName());
060    CONF.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, SystemCoprocessor.class.getName());
061  }
062  private static final TableName TABLENAME = TableName.valueOf("TestCoprocessorConfiguration");
063  private static final RegionInfo REGIONINFO = RegionInfoBuilder.newBuilder(TABLENAME).build();
064  private static final TableDescriptor TABLEDESC;
065  static {
066    try {
067      TABLEDESC = TableDescriptorBuilder.newBuilder(TABLENAME)
068        .setCoprocessor(TableCoprocessor.class.getName()).build();
069    } catch (IOException e) {
070      throw new RuntimeException(e);
071    }
072  }
073
074  // use atomic types in case coprocessor loading is ever multithreaded, also
075  // so we can mutate them even though they are declared final here
076  private static final AtomicBoolean systemCoprocessorLoaded = new AtomicBoolean();
077  private static final AtomicBoolean tableCoprocessorLoaded = new AtomicBoolean();
078
079  public static class SystemCoprocessor
080    implements MasterCoprocessor, RegionCoprocessor, RegionServerCoprocessor {
081    @Override
082    public void start(CoprocessorEnvironment env) throws IOException {
083      systemCoprocessorLoaded.set(true);
084    }
085
086    @Override
087    public void stop(CoprocessorEnvironment env) throws IOException {
088    }
089  }
090
091  public static class TableCoprocessor implements RegionCoprocessor {
092    @Override
093    public void start(CoprocessorEnvironment env) throws IOException {
094      tableCoprocessorLoaded.set(true);
095    }
096
097    @Override
098    public void stop(CoprocessorEnvironment env) throws IOException {
099    }
100  }
101
102  @Test
103  public void testRegionCoprocessorHostDefaults() throws Exception {
104    Configuration conf = new Configuration(CONF);
105    HRegion region = mock(HRegion.class);
106    when(region.getRegionInfo()).thenReturn(REGIONINFO);
107    when(region.getTableDescriptor()).thenReturn(TABLEDESC);
108    RegionServerServices rsServices = mock(RegionServerServices.class);
109    systemCoprocessorLoaded.set(false);
110    tableCoprocessorLoaded.set(false);
111    new RegionCoprocessorHost(region, rsServices, conf);
112    assertEquals(CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED, systemCoprocessorLoaded.get(),
113      "System coprocessors loading default was not honored");
114    assertEquals(
115      CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED
116        && CoprocessorHost.DEFAULT_USER_COPROCESSORS_ENABLED,
117      tableCoprocessorLoaded.get(), "Table coprocessors loading default was not honored");
118  }
119
120  @Test
121  public void testRegionServerCoprocessorHostDefaults() throws Exception {
122    Configuration conf = new Configuration(CONF);
123    RegionServerServices rsServices = mock(RegionServerServices.class);
124    systemCoprocessorLoaded.set(false);
125    new RegionServerCoprocessorHost(rsServices, conf);
126    assertEquals(CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED, systemCoprocessorLoaded.get(),
127      "System coprocessors loading default was not honored");
128  }
129
130  @Test
131  public void testMasterCoprocessorHostDefaults() throws Exception {
132    Configuration conf = new Configuration(CONF);
133    MasterServices masterServices = mock(MasterServices.class);
134    systemCoprocessorLoaded.set(false);
135    new MasterCoprocessorHost(masterServices, conf);
136    assertEquals(CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED, systemCoprocessorLoaded.get(),
137      "System coprocessors loading default was not honored");
138  }
139
140  @Test
141  public void testRegionCoprocessorHostAllDisabled() throws Exception {
142    Configuration conf = new Configuration(CONF);
143    conf.setBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY, false);
144    HRegion region = mock(HRegion.class);
145    when(region.getRegionInfo()).thenReturn(REGIONINFO);
146    when(region.getTableDescriptor()).thenReturn(TABLEDESC);
147    RegionServerServices rsServices = mock(RegionServerServices.class);
148    systemCoprocessorLoaded.set(false);
149    tableCoprocessorLoaded.set(false);
150    new RegionCoprocessorHost(region, rsServices, conf);
151    assertFalse(systemCoprocessorLoaded.get(), "System coprocessors should not have been loaded");
152    assertFalse(tableCoprocessorLoaded.get(), "Table coprocessors should not have been loaded");
153  }
154
155  @Test
156  public void testRegionCoprocessorHostTableLoadingDisabled() throws Exception {
157    Configuration conf = new Configuration(CONF);
158    conf.setBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY, true); // if defaults change
159    conf.setBoolean(CoprocessorHost.USER_COPROCESSORS_ENABLED_CONF_KEY, false);
160    HRegion region = mock(HRegion.class);
161    when(region.getRegionInfo()).thenReturn(REGIONINFO);
162    when(region.getTableDescriptor()).thenReturn(TABLEDESC);
163    RegionServerServices rsServices = mock(RegionServerServices.class);
164    systemCoprocessorLoaded.set(false);
165    tableCoprocessorLoaded.set(false);
166    new RegionCoprocessorHost(region, rsServices, conf);
167    assertTrue(systemCoprocessorLoaded.get(), "System coprocessors should have been loaded");
168    assertFalse(tableCoprocessorLoaded.get(), "Table coprocessors should not have been loaded");
169  }
170
171  /**
172   * Rough test that Coprocessor Environment is Read-Only. Just check a random CP and see that it
173   * returns a read-only config.
174   */
175  @Test
176  public void testReadOnlyConfiguration() throws Exception {
177    Configuration conf = new Configuration(CONF);
178    HRegion region = mock(HRegion.class);
179    when(region.getRegionInfo()).thenReturn(REGIONINFO);
180    when(region.getTableDescriptor()).thenReturn(TABLEDESC);
181    RegionServerServices rsServices = mock(RegionServerServices.class);
182    RegionCoprocessorHost rcp = new RegionCoprocessorHost(region, rsServices, conf);
183    boolean found = false;
184    for (String cpStr : rcp.getCoprocessors()) {
185      CoprocessorEnvironment cpenv = rcp.findCoprocessorEnvironment(cpStr);
186      if (cpenv != null) {
187        found = true;
188      }
189      Configuration c = cpenv.getConfiguration();
190      assertThrows(UnsupportedOperationException.class,
191        () -> c.set("one.two.three", "four.five.six"));
192    }
193    assertTrue(found, "Should be at least one CP found");
194  }
195}