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.rsgroup;
019
020import static org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.META_FAMILY_BYTES;
021import static org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.META_QUALIFIER_BYTES;
022import static org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME;
023import static org.junit.jupiter.api.Assertions.assertEquals;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025
026import java.io.IOException;
027import java.util.concurrent.CountDownLatch;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.TableDescriptors;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Get;
033import org.apache.hadoop.hbase.client.Put;
034import org.apache.hadoop.hbase.client.Result;
035import org.apache.hadoop.hbase.client.Table;
036import org.apache.hadoop.hbase.client.TableDescriptor;
037import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
038import org.apache.hadoop.hbase.master.HMaster;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.apache.hadoop.hbase.testclassification.RSGroupTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.apache.zookeeper.KeeperException;
043import org.junit.jupiter.api.AfterAll;
044import org.junit.jupiter.api.BeforeAll;
045import org.junit.jupiter.api.Tag;
046import org.junit.jupiter.api.Test;
047import org.junit.jupiter.api.TestInfo;
048
049import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
050import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupProtos;
051
052/**
053 * Testcase for HBASE-22819
054 */
055@Tag(RSGroupTests.TAG)
056@Tag(MediumTests.TAG)
057public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
058
059  private static String TABLE_NAME_PREFIX = "Table_";
060
061  private static int NUM_TABLES = 10;
062
063  private static byte[] FAMILY = Bytes.toBytes("family");
064
065  private static RSGroupAdminClient RS_GROUP_ADMIN_CLIENT;
066
067  @BeforeAll
068  public static void setUp() throws Exception {
069    TEST_UTIL.getConfiguration().setClass(HConstants.MASTER_IMPL, HMasterForTest.class,
070      HMaster.class);
071    // confirm that we could enable rs group by setting the old CP.
072    TEST_UTIL.getConfiguration().setBoolean(RSGroupUtil.RS_GROUP_ENABLED, false);
073    TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
074      RSGroupAdminEndpoint.class.getName());
075    setUpTestBeforeClass();
076    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
077    for (int i = 0; i < NUM_TABLES; i++) {
078      TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME_PREFIX + i), FAMILY);
079    }
080  }
081
082  @AfterAll
083  public static void tearDown() throws Exception {
084    tearDownAfterClass();
085  }
086
087  private static CountDownLatch RESUME = new CountDownLatch(1);
088
089  public static final class HMasterForTest extends HMaster {
090
091    public HMasterForTest(Configuration conf) throws IOException, KeeperException {
092      super(conf);
093    }
094
095    @Override
096    public TableDescriptors getTableDescriptors() {
097      if (RESUME != null) {
098        for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
099          if (element.getMethodName().equals("migrate")) {
100            try {
101              RESUME.await();
102            } catch (InterruptedException e) {
103            }
104            RESUME = null;
105            break;
106          }
107        }
108      }
109      return super.getTableDescriptors();
110    }
111  }
112
113  @Test
114  public void testMigrate(TestInfo testInfo) throws IOException, InterruptedException {
115    String groupName = getNameWithoutIndex(testInfo.getTestMethod().get().getName());
116    addGroup(groupName, TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().size() - 1);
117    RSGroupInfo rsGroupInfo = ADMIN.getRSGroup(groupName);
118    assertTrue(rsGroupInfo.getTables().isEmpty());
119    for (int i = 0; i < NUM_TABLES; i++) {
120      rsGroupInfo.addTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
121    }
122    try (Table table = TEST_UTIL.getConnection().getTable(RSGROUP_TABLE_NAME)) {
123      RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(rsGroupInfo);
124      Put p = new Put(Bytes.toBytes(rsGroupInfo.getName()));
125      p.addColumn(META_FAMILY_BYTES, META_QUALIFIER_BYTES, proto.toByteArray());
126      table.put(p);
127    }
128    TEST_UTIL.getMiniHBaseCluster().stopMaster(0).join();
129    RESUME = new CountDownLatch(1);
130    TEST_UTIL.getMiniHBaseCluster().startMaster();
131    TEST_UTIL.invalidateConnection();
132    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
133
134    // wait until we can get the rs group info for a table
135    TEST_UTIL.waitFor(30000, () -> {
136      try {
137        RS_GROUP_ADMIN_CLIENT.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + 0));
138        return true;
139      } catch (IOException e) {
140        return false;
141      }
142    });
143    // confirm that before migrating, we could still get the correct rs group for a table.
144    for (int i = 0; i < NUM_TABLES; i++) {
145      RSGroupInfo info =
146        RS_GROUP_ADMIN_CLIENT.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
147      assertEquals(rsGroupInfo.getName(), info.getName());
148      assertEquals(NUM_TABLES, info.getTables().size());
149    }
150    RESUME.countDown();
151    TEST_UTIL.waitFor(60000, () -> {
152      for (int i = 0; i < NUM_TABLES; i++) {
153        TableDescriptor td;
154        try {
155          td = TEST_UTIL.getAdmin().getDescriptor(TableName.valueOf(TABLE_NAME_PREFIX + i));
156        } catch (IOException e) {
157          return false;
158        }
159        if (!rsGroupInfo.getName().equals(td.getRegionServerGroup().orElse(null))) {
160          return false;
161        }
162      }
163      return true;
164    });
165    // make sure that we persist the result to hbase, where we delete all the tables in the rs
166    // group.
167    TEST_UTIL.waitFor(30000, () -> {
168      try (Table table = TEST_UTIL.getConnection().getTable(RSGROUP_TABLE_NAME)) {
169        Result result = table.get(new Get(Bytes.toBytes(rsGroupInfo.getName())));
170        RSGroupProtos.RSGroupInfo proto = RSGroupProtos.RSGroupInfo
171          .parseFrom(result.getValue(META_FAMILY_BYTES, META_QUALIFIER_BYTES));
172        RSGroupInfo gi = ProtobufUtil.toGroupInfo(proto);
173        return gi.getTables().isEmpty();
174      }
175    });
176    // make sure that the migrate thread has quit.
177    TEST_UTIL.waitFor(30000, () -> Thread.getAllStackTraces().keySet().stream()
178      .noneMatch(t -> t.getName().equals(RSGroupInfoManagerImpl.MIGRATE_THREAD_NAME)));
179    // make sure we could still get the correct rs group info after migration
180    for (int i = 0; i < NUM_TABLES; i++) {
181      RSGroupInfo info =
182        RS_GROUP_ADMIN_CLIENT.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
183      assertEquals(rsGroupInfo.getName(), info.getName());
184      assertEquals(NUM_TABLES, info.getTables().size());
185    }
186  }
187}