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.regionserver;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023import static org.junit.jupiter.api.Assertions.assertNull;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025
026import java.io.IOException;
027import java.util.concurrent.TimeUnit;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.HConstants;
031import org.apache.hadoop.hbase.JMXListener;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.Waiter;
034import org.apache.hadoop.hbase.client.Admin;
035import org.apache.hadoop.hbase.client.Connection;
036import org.apache.hadoop.hbase.client.ConnectionFactory;
037import org.apache.hadoop.hbase.client.RegionInfo;
038import org.apache.hadoop.hbase.client.RegionLocator;
039import org.apache.hadoop.hbase.client.Table;
040import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
041import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
042import org.apache.hadoop.hbase.master.HMaster;
043import org.apache.hadoop.hbase.regionserver.compactions.CompactionConfiguration;
044import org.apache.hadoop.hbase.testclassification.MediumTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.junit.jupiter.api.AfterAll;
047import org.junit.jupiter.api.BeforeAll;
048import org.junit.jupiter.api.BeforeEach;
049import org.junit.jupiter.api.Tag;
050import org.junit.jupiter.api.Test;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054/**
055 * Verify that the Online config Changes on the HRegionServer side are actually happening. We should
056 * add tests for important configurations which will be changed online.
057 */
058
059@Tag(MediumTests.TAG)
060public class TestRegionServerOnlineConfigChange {
061
062  private static final Logger LOG =
063    LoggerFactory.getLogger(TestRegionServerOnlineConfigChange.class.getName());
064  private static final long WAIT_TIMEOUT = TimeUnit.MINUTES.toMillis(2);
065  private static HBaseTestingUtil hbaseTestingUtility = new HBaseTestingUtil();
066  private static Configuration conf = null;
067
068  private static Table t1 = null;
069  private static HRegionServer rs1 = null;
070  private static HMaster hMaster = null;
071  private static byte[] r1name = null;
072  private static Region r1 = null;
073
074  private final static String table1Str = "table1";
075  private final static String columnFamily1Str = "columnFamily1";
076  private final static TableName TABLE1 = TableName.valueOf(table1Str);
077  private final static byte[] COLUMN_FAMILY1 = Bytes.toBytes(columnFamily1Str);
078  private final static long MAX_FILE_SIZE = 20 * 1024 * 1024L;
079
080  @BeforeAll
081  public static void setUpBeforeClass() throws Exception {
082    conf = hbaseTestingUtility.getConfiguration();
083    hbaseTestingUtility.startMiniCluster(2);
084    t1 = hbaseTestingUtility.createTable(
085      TableDescriptorBuilder.newBuilder(TABLE1).setMaxFileSize(MAX_FILE_SIZE).build(),
086      new byte[][] { COLUMN_FAMILY1 }, conf);
087  }
088
089  @AfterAll
090  public static void tearDown() throws Exception {
091    hbaseTestingUtility.shutdownMiniCluster();
092  }
093
094  @BeforeEach
095  public void setUp() throws Exception {
096    try (RegionLocator locator = hbaseTestingUtility.getConnection().getRegionLocator(TABLE1)) {
097      RegionInfo firstHRI = locator.getAllRegionLocations().get(0).getRegion();
098      r1name = firstHRI.getRegionName();
099      rs1 = hbaseTestingUtility.getHBaseCluster()
100        .getRegionServer(hbaseTestingUtility.getHBaseCluster().getServerWith(r1name));
101      r1 = rs1.getRegion(r1name);
102      hMaster = hbaseTestingUtility.getHBaseCluster().getMaster();
103    }
104  }
105
106  /**
107   * Check if the number of compaction threads changes online
108   */
109  @Test
110  public void testNumCompactionThreadsOnlineChange() {
111    assertNotNull(rs1.getCompactSplitThread());
112    int newNumSmallThreads = rs1.getCompactSplitThread().getSmallCompactionThreadNum() + 1;
113    int newNumLargeThreads = rs1.getCompactSplitThread().getLargeCompactionThreadNum() + 1;
114
115    conf.setInt("hbase.regionserver.thread.compaction.small", newNumSmallThreads);
116    conf.setInt("hbase.regionserver.thread.compaction.large", newNumLargeThreads);
117    rs1.getConfigurationManager().notifyAllObservers(conf);
118
119    assertEquals(newNumSmallThreads, rs1.getCompactSplitThread().getSmallCompactionThreadNum());
120    assertEquals(newNumLargeThreads, rs1.getCompactSplitThread().getLargeCompactionThreadNum());
121  }
122
123  /**
124   * Test that the configurations in the CompactionConfiguration class change properly.
125   */
126  @Test
127  public void testCompactionConfigurationOnlineChange() throws IOException {
128    String strPrefix = "hbase.hstore.compaction.";
129    Store s = r1.getStore(COLUMN_FAMILY1);
130    if (!(s instanceof HStore)) {
131      LOG.error("Can't test the compaction configuration of HStore class. "
132        + "Got a different implementation other than HStore");
133      return;
134    }
135    HStore hstore = (HStore) s;
136
137    // Set the new compaction ratio to a different value.
138    double newCompactionRatio =
139      hstore.getStoreEngine().getCompactionPolicy().getConf().getCompactionRatio() + 0.1;
140    conf.setFloat(strPrefix + "ratio", (float) newCompactionRatio);
141
142    // Notify all the observers, which includes the Store object.
143    rs1.getConfigurationManager().notifyAllObservers(conf);
144
145    // Check if the compaction ratio got updated in the Compaction Configuration
146    assertEquals(newCompactionRatio,
147      hstore.getStoreEngine().getCompactionPolicy().getConf().getCompactionRatio(), 0.00001);
148
149    // Check if the off peak compaction ratio gets updated.
150    double newOffPeakCompactionRatio =
151      hstore.getStoreEngine().getCompactionPolicy().getConf().getCompactionRatioOffPeak() + 0.1;
152    conf.setFloat(strPrefix + "ratio.offpeak", (float) newOffPeakCompactionRatio);
153    rs1.getConfigurationManager().notifyAllObservers(conf);
154    assertEquals(newOffPeakCompactionRatio,
155      hstore.getStoreEngine().getCompactionPolicy().getConf().getCompactionRatioOffPeak(), 0.00001);
156
157    // Check if the throttle point gets updated.
158    long newThrottlePoint =
159      hstore.getStoreEngine().getCompactionPolicy().getConf().getThrottlePoint() + 10;
160    conf.setLong("hbase.regionserver.thread.compaction.throttle", newThrottlePoint);
161    rs1.getConfigurationManager().notifyAllObservers(conf);
162    assertEquals(newThrottlePoint,
163      hstore.getStoreEngine().getCompactionPolicy().getConf().getThrottlePoint());
164
165    // Check if the minFilesToCompact gets updated.
166    int newMinFilesToCompact =
167      hstore.getStoreEngine().getCompactionPolicy().getConf().getMinFilesToCompact() + 1;
168    conf.setLong(strPrefix + "min", newMinFilesToCompact);
169    rs1.getConfigurationManager().notifyAllObservers(conf);
170    assertEquals(newMinFilesToCompact,
171      hstore.getStoreEngine().getCompactionPolicy().getConf().getMinFilesToCompact());
172
173    // Check if the maxFilesToCompact gets updated.
174    int newMaxFilesToCompact =
175      hstore.getStoreEngine().getCompactionPolicy().getConf().getMaxFilesToCompact() + 1;
176    conf.setLong(strPrefix + "max", newMaxFilesToCompact);
177    rs1.getConfigurationManager().notifyAllObservers(conf);
178    assertEquals(newMaxFilesToCompact,
179      hstore.getStoreEngine().getCompactionPolicy().getConf().getMaxFilesToCompact());
180
181    // Check OffPeak hours is updated in an online fashion.
182    conf.setLong(CompactionConfiguration.HBASE_HSTORE_OFFPEAK_START_HOUR, 6);
183    conf.setLong(CompactionConfiguration.HBASE_HSTORE_OFFPEAK_END_HOUR, 7);
184    rs1.getConfigurationManager().notifyAllObservers(conf);
185    assertFalse(hstore.getOffPeakHours().isOffPeakHour(4));
186
187    // Check if the minCompactSize gets updated.
188    long newMinCompactSize =
189      hstore.getStoreEngine().getCompactionPolicy().getConf().getMinCompactSize() + 1;
190    conf.setLong(strPrefix + "min.size", newMinCompactSize);
191    rs1.getConfigurationManager().notifyAllObservers(conf);
192    assertEquals(newMinCompactSize,
193      hstore.getStoreEngine().getCompactionPolicy().getConf().getMinCompactSize());
194
195    // Check if the maxCompactSize gets updated.
196    long newMaxCompactSize =
197      hstore.getStoreEngine().getCompactionPolicy().getConf().getMaxCompactSize() - 1;
198    conf.setLong(strPrefix + "max.size", newMaxCompactSize);
199    rs1.getConfigurationManager().notifyAllObservers(conf);
200    assertEquals(newMaxCompactSize,
201      hstore.getStoreEngine().getCompactionPolicy().getConf().getMaxCompactSize());
202    // Check if the offPeakMaxCompactSize gets updated.
203    long newOffpeakMaxCompactSize =
204      hstore.getStoreEngine().getCompactionPolicy().getConf().getOffPeakMaxCompactSize() - 1;
205    conf.setLong(CompactionConfiguration.HBASE_HSTORE_COMPACTION_MAX_SIZE_OFFPEAK_KEY,
206      newOffpeakMaxCompactSize);
207    rs1.getConfigurationManager().notifyAllObservers(conf);
208    assertEquals(newOffpeakMaxCompactSize,
209      hstore.getStoreEngine().getCompactionPolicy().getConf().getOffPeakMaxCompactSize());
210
211    // Check if majorCompactionPeriod gets updated.
212    long newMajorCompactionPeriod =
213      hstore.getStoreEngine().getCompactionPolicy().getConf().getMajorCompactionPeriod() + 10;
214    conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, newMajorCompactionPeriod);
215    rs1.getConfigurationManager().notifyAllObservers(conf);
216    assertEquals(newMajorCompactionPeriod,
217      hstore.getStoreEngine().getCompactionPolicy().getConf().getMajorCompactionPeriod());
218
219    // Check if majorCompactionJitter gets updated.
220    float newMajorCompactionJitter =
221      hstore.getStoreEngine().getCompactionPolicy().getConf().getMajorCompactionJitter() + 0.02F;
222    conf.setFloat("hbase.hregion.majorcompaction.jitter", newMajorCompactionJitter);
223    rs1.getConfigurationManager().notifyAllObservers(conf);
224    assertEquals(newMajorCompactionJitter,
225      hstore.getStoreEngine().getCompactionPolicy().getConf().getMajorCompactionJitter(), 0.00001);
226  }
227
228  @Test
229  public void removeClosedRegionFromConfigurationManager() throws Exception {
230    try (Connection connection = ConnectionFactory.createConnection(conf)) {
231      Admin admin = connection.getAdmin();
232      assertTrue(rs1.getConfigurationManager().containsObserver(r1),
233        "The open region doesn't register as a ConfigurationObserver");
234      admin.move(r1name);
235      hbaseTestingUtility.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
236        @Override
237        public boolean evaluate() throws Exception {
238          return rs1.getOnlineRegion(r1name) == null;
239        }
240      });
241      assertFalse(rs1.getConfigurationManager().containsObserver(r1),
242        "The closed region is not removed from ConfigurationManager");
243      admin.move(r1name, rs1.getServerName());
244      hbaseTestingUtility.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
245        @Override
246        public boolean evaluate() throws Exception {
247          return rs1.getOnlineRegion(r1name) != null;
248        }
249      });
250    }
251  }
252
253  @Test
254  public void testStoreConfigurationOnlineChange() {
255    rs1.getConfigurationManager().notifyAllObservers(conf);
256    long actualMaxFileSize = r1.getStore(COLUMN_FAMILY1).getReadOnlyConfiguration()
257      .getLong(TableDescriptorBuilder.MAX_FILESIZE, -1);
258    assertEquals(MAX_FILE_SIZE, actualMaxFileSize);
259  }
260
261  @Test
262  public void testCoprocessorConfigurationOnlineChange() {
263    assertNull(rs1.getRegionServerCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
264    // Update configuration directly to simulate dynamic configuration reload
265    Configuration rsConf = rs1.getConfiguration();
266    rsConf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
267    rs1.getConfigurationManager().notifyAllObservers(rsConf);
268    assertNotNull(
269      rs1.getRegionServerCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
270  }
271
272  @Test
273  public void testCoprocessorConfigurationOnlineChangeOnMaster() {
274    assertNull(hMaster.getMasterCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
275    // Update configuration directly to simulate dynamic configuration reload
276    Configuration masterConf = hMaster.getConfiguration();
277    masterConf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
278    assertFalse(hMaster.isInMaintenanceMode());
279    hMaster.getConfigurationManager().notifyAllObservers(masterConf);
280    assertNotNull(hMaster.getMasterCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
281  }
282
283}