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;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotEquals;
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.lang.invoke.MethodHandles;
028import java.lang.reflect.Field;
029import java.lang.reflect.Modifier;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.client.Connection;
032import org.apache.hadoop.hbase.client.ConnectionFactory;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.master.HMaster;
035import org.apache.hadoop.hbase.testclassification.MediumTests;
036import org.apache.hadoop.hbase.testclassification.MiscTests;
037import org.apache.hadoop.hbase.util.Bytes;
038import org.apache.hadoop.hbase.util.Pair;
039import org.junit.jupiter.api.AfterAll;
040import org.junit.jupiter.api.BeforeAll;
041import org.junit.jupiter.api.Tag;
042import org.junit.jupiter.api.Test;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046/**
047 * Test {@link org.apache.hadoop.hbase.TestMetaTableForReplica}.
048 */
049@Tag(MiscTests.TAG)
050@Tag(MediumTests.TAG)
051@SuppressWarnings("deprecation")
052public class TestMetaTableForReplica {
053
054  private static final Logger LOG = LoggerFactory.getLogger(TestMetaTableForReplica.class);
055  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
056  private static Connection connection;
057  private static Field metaTableName;
058  private static Object originalMetaTableName;
059
060  @BeforeAll
061  public static void beforeClass() throws Exception {
062    Configuration c = UTIL.getConfiguration();
063    // quicker heartbeat interval for faster DN death notification
064    c.setInt("hbase.ipc.client.connect.max.retries", 1);
065    c.setInt(HConstants.ZK_SESSION_TIMEOUT, 1000);
066    // Start cluster having non-default hbase meta table name
067    UTIL.startMiniCluster(3);
068    connection = ConnectionFactory.createConnection(c);
069    // Save the original value of META_TABLE_NAME before any test runs.x
070    metaTableName = TableName.class.getDeclaredField("META_TABLE_NAME");
071    originalMetaTableName = metaTableName.get(null);
072  }
073
074  @AfterAll
075  public static void afterClass() throws Exception {
076    connection.close();
077    UTIL.shutdownMiniCluster();
078  }
079
080  @Test
081  public void testStateOfMetaForReplica() {
082    HMaster m = UTIL.getMiniHBaseCluster().getMaster();
083    assertTrue(m.waitForMetaOnline());
084  }
085
086  @Test
087  public void testMetaTableNameForReplicaWithoutSuffix() throws IOException {
088    testNameOfMetaForReplica();
089    testGetNonExistentRegionFromMetaFromReplica();
090    testGetExistentRegionFromMetaFromReplica();
091  }
092
093  private void testNameOfMetaForReplica() {
094    // Check the correctness of the meta table for replica
095    String metaTableName = TableName.META_TABLE_NAME.getNameWithNamespaceInclAsString();
096    assertNotNull(metaTableName);
097
098    // Check if name of the meta table for replica is same as the default meta table
099    assertEquals(0,
100      TableName.META_TABLE_NAME.compareTo(TableName.getDefaultNameOfMetaForReplica()));
101  }
102
103  private void testGetNonExistentRegionFromMetaFromReplica() throws IOException {
104    LOG.info("Started testGetNonExistentRegionFromMetaFromReplica");
105    Pair<RegionInfo, ServerName> pair =
106      MetaTableAccessor.getRegion(connection, Bytes.toBytes("nonexistent-region"));
107    assertNull(pair);
108    LOG.info("Finished testGetNonExistentRegionFromMetaFromReplica");
109  }
110
111  private void testGetExistentRegionFromMetaFromReplica() throws IOException {
112    final TableName tableName = TableName.valueOf("testMetaTableNameForReplicaWithoutSuffix");
113    LOG.info("Started " + tableName);
114    UTIL.createTable(tableName, HConstants.CATALOG_FAMILY);
115    assertEquals(1, MetaTableAccessor.getTableRegions(connection, tableName).size());
116  }
117
118  @Test
119  public void testMetaTableNameForReplicaWithSuffix() throws Exception {
120    // This test actively changes the META_TABLE_NAME to a non-default value and verifies it.
121    Configuration conf = HBaseConfiguration.create();
122    String suffix = "replica1";
123    conf.set(HConstants.HBASE_META_TABLE_SUFFIX, suffix);
124
125    // Re-initialize the static final META_TABLE_NAME for the testing to a non-default value.
126    TableName expectedMetaTableName = TableName.initializeHbaseMetaTableName(conf);
127    setStaticFinalField(metaTableName, expectedMetaTableName);
128
129    TableName currentMetaName = TableName.META_TABLE_NAME;
130    TableName defaultMetaName = TableName.getDefaultNameOfMetaForReplica();
131
132    // The current meta table name is not the default one.
133    assertNotEquals(defaultMetaName, currentMetaName,
134      "META_TABLE_NAME should not be the default. ");
135
136    // The current meta table name has the configured suffix.
137    assertEquals(expectedMetaTableName, currentMetaName,
138      "META_TABLE_NAME should have the configured suffix");
139
140    // restore default value of META_TABLE_NAME
141    setDefaultMetaTableName();
142  }
143
144  private static void setDefaultMetaTableName() throws Exception {
145    if (originalMetaTableName != null) {
146      setStaticFinalField(metaTableName, originalMetaTableName);
147    }
148  }
149
150  /**
151   * A helper method to modify a static final field using reflection. This is necessary for testing
152   * code that reads a configuration only once during class loading.
153   * @param field    The field to modify.
154   * @param newValue The new value to set.
155   * @throws Exception if reflection fails.
156   */
157  private static void setStaticFinalField(Field field, Object newValue) throws Exception {
158    field.setAccessible(true);
159    // Using MethodHandles to get a trusted lookup with the necessary permissions to modify it.
160    // NOTE: For this to work, the JVM running the test must be started with arguments like:
161    // --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
162    var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
163    var handle = lookup.findVarHandle(Field.class, "modifiers", int.class);
164    handle.set(field, field.getModifiers() & ~Modifier.FINAL);
165    field.set(null, newValue);
166  }
167}