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.util;
019
020import static org.apache.hadoop.hbase.HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY;
021import static org.junit.jupiter.api.Assertions.assertArrayEquals;
022import static org.junit.jupiter.api.Assertions.assertEquals;
023import static org.junit.jupiter.api.Assertions.assertNotNull;
024import static org.junit.jupiter.api.Assertions.assertNull;
025import static org.junit.jupiter.api.Assertions.assertTrue;
026
027import java.util.Arrays;
028import java.util.List;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
031import org.apache.hadoop.hbase.security.access.BulkLoadReadOnlyController;
032import org.apache.hadoop.hbase.security.access.EndpointReadOnlyController;
033import org.apache.hadoop.hbase.security.access.MasterReadOnlyController;
034import org.apache.hadoop.hbase.security.access.RegionReadOnlyController;
035import org.apache.hadoop.hbase.security.access.RegionServerReadOnlyController;
036import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
037import org.apache.hadoop.hbase.testclassification.SmallTests;
038import org.junit.jupiter.api.BeforeEach;
039import org.junit.jupiter.api.Tag;
040import org.junit.jupiter.api.Test;
041
042@Tag(CoprocessorTests.TAG)
043@Tag(SmallTests.TAG)
044public class TestCoprocessorConfigurationUtil {
045
046  private Configuration conf;
047  private String key;
048
049  @BeforeEach
050  public void setUp() {
051    conf = new Configuration();
052    key = "test.key";
053  }
054
055  @Test
056  public void testAddCoprocessorsEmptyCPList() {
057    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1", "cp2"));
058    assertArrayEquals(new String[] { "cp1", "cp2" }, conf.getStrings(key));
059  }
060
061  @Test
062  public void testAddCoprocessorsNonEmptyCPList() {
063    conf.setStrings(key, "cp1");
064    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1", "cp2"));
065    assertArrayEquals(new String[] { "cp1", "cp2" }, conf.getStrings(key));
066  }
067
068  @Test
069  public void testAddCoprocessorsNoChange() {
070    conf.setStrings(key, "cp1");
071    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1"));
072    assertArrayEquals(new String[] { "cp1" }, conf.getStrings(key));
073  }
074
075  @Test
076  public void testAddCoprocessorsIdempotent() {
077    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1", "cp2"));
078    // Call again
079    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1", "cp2"));
080    assertArrayEquals(new String[] { "cp1", "cp2" }, conf.getStrings(key));
081  }
082
083  @Test
084  public void testAddCoprocessorsIdempotentWithOverlap() {
085    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1"));
086    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp1", "cp2"));
087    CoprocessorConfigurationUtil.addCoprocessors(conf, key, List.of("cp2"));
088    assertArrayEquals(new String[] { "cp1", "cp2" }, conf.getStrings(key));
089  }
090
091  @Test
092  public void testRemoveCoprocessorsEmptyCPList() {
093    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp1"));
094    assertNull(conf.getStrings(key));
095  }
096
097  @Test
098  public void testRemoveCoprocessorsNonEmptyCPList() {
099    conf.setStrings(key, "cp1", "cp2", "cp3");
100    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp2"));
101    assertArrayEquals(new String[] { "cp1", "cp3" }, conf.getStrings(key));
102  }
103
104  @Test
105  public void testRemoveCoprocessorsNoChange() {
106    conf.setStrings(key, "cp1");
107    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp2"));
108    assertArrayEquals(new String[] { "cp1" }, conf.getStrings(key));
109  }
110
111  @Test
112  public void testRemoveCoprocessorsIdempotent() {
113    conf.setStrings(key, "cp1", "cp2");
114    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp2"));
115    // Call again
116    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp2"));
117    assertArrayEquals(new String[] { "cp1" }, conf.getStrings(key));
118  }
119
120  @Test
121  public void testRemoveCoprocessorsIdempotentWhenNotPresent() {
122    conf.setStrings(key, "cp1");
123    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp2"));
124    CoprocessorConfigurationUtil.removeCoprocessors(conf, key, List.of("cp2"));
125    assertArrayEquals(new String[] { "cp1" }, conf.getStrings(key));
126  }
127
128  private void assertEnableReadOnlyMode(String key, List<String> expected) {
129    conf.setBoolean(HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
130    CoprocessorConfigurationUtil.syncReadOnlyConfigurations(conf, key);
131    String[] result = conf.getStrings(key);
132    assertNotNull(result);
133    assertEquals(expected.size(), result.length);
134    assertTrue(Arrays.asList(result).containsAll(expected));
135  }
136
137  @Test
138  public void testSyncReadOnlyConfigurationsReadOnlyEnableAllKeys() {
139    assertEnableReadOnlyMode(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
140      List.of(MasterReadOnlyController.class.getName()));
141
142    assertEnableReadOnlyMode(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
143      List.of(RegionServerReadOnlyController.class.getName()));
144    assertEnableReadOnlyMode(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
145      List.of(RegionReadOnlyController.class.getName(), BulkLoadReadOnlyController.class.getName(),
146        EndpointReadOnlyController.class.getName()));
147  }
148
149  private void assertDisableReadOnlyMode(String key, List<String> initialCoprocs) {
150    conf.setStrings(key, initialCoprocs.toArray(new String[0]));
151    conf.setBoolean(HBASE_GLOBAL_READONLY_ENABLED_KEY, false);
152    CoprocessorConfigurationUtil.syncReadOnlyConfigurations(conf, key);
153    String[] result = conf.getStrings(key);
154    assertTrue(result == null || result.length == 0);
155  }
156
157  @Test
158  public void testSyncReadOnlyConfigurationsReadOnlyDisableAllKeys() {
159    assertDisableReadOnlyMode(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
160      List.of(MasterReadOnlyController.class.getName()));
161
162    assertDisableReadOnlyMode(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
163      List.of(RegionServerReadOnlyController.class.getName()));
164
165    assertDisableReadOnlyMode(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
166      List.of(RegionReadOnlyController.class.getName(), BulkLoadReadOnlyController.class.getName(),
167        EndpointReadOnlyController.class.getName()));
168  }
169
170  @Test
171  public void testSyncReadOnlyConfigurationsReadOnlyEnablePreservesExistingCoprocessors() {
172    String key = CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY;
173    conf.setStrings(key, "existingCp");
174    conf.setBoolean(HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
175    CoprocessorConfigurationUtil.syncReadOnlyConfigurations(conf, key);
176    List<String> result = Arrays.asList(conf.getStrings(key));
177    assertTrue(result.contains("existingCp"));
178    assertTrue(result.contains(MasterReadOnlyController.class.getName()));
179  }
180
181  @Test
182  public void testSyncReadOnlyConfigurationsReadOnlyDisableRemovesOnlyReadOnlyCoprocessor() {
183    Configuration conf = new Configuration(false);
184    String key = CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY;
185    String existingCp = "org.example.OtherCP";
186    conf.setStrings(key, existingCp, MasterReadOnlyController.class.getName());
187    CoprocessorConfigurationUtil.syncReadOnlyConfigurations(conf, key);
188    String[] cps = conf.getStrings(key);
189    assertNotNull(cps);
190    assertEquals(1, cps.length);
191    assertEquals(existingCp, cps[0]);
192  }
193
194  @Test
195  public void testSyncReadOnlyConfigurationsIsIdempotent() {
196    Configuration conf = new Configuration(false);
197    conf.setBoolean(HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
198    String key = CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY;
199    CoprocessorConfigurationUtil.syncReadOnlyConfigurations(conf, key);
200    CoprocessorConfigurationUtil.syncReadOnlyConfigurations(conf, key);
201    String[] cps = conf.getStrings(key);
202    assertNotNull(cps);
203    assertEquals(1, cps.length);
204  }
205}