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 java.io.IOException;
021import java.util.EnumSet;
022import java.util.List;
023import java.util.Optional;
024import java.util.concurrent.CompletableFuture;
025import java.util.concurrent.atomic.AtomicInteger;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.ClusterMetrics.Option;
028import org.apache.hadoop.hbase.Waiter.Predicate;
029import org.apache.hadoop.hbase.client.Admin;
030import org.apache.hadoop.hbase.client.AsyncAdmin;
031import org.apache.hadoop.hbase.client.AsyncConnection;
032import org.apache.hadoop.hbase.client.ConnectionFactory;
033import org.apache.hadoop.hbase.client.Put;
034import org.apache.hadoop.hbase.client.RegionStatesCount;
035import org.apache.hadoop.hbase.client.Table;
036import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
037import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
038import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
039import org.apache.hadoop.hbase.coprocessor.MasterObserver;
040import org.apache.hadoop.hbase.coprocessor.ObserverContext;
041import org.apache.hadoop.hbase.master.HMaster;
042import org.apache.hadoop.hbase.regionserver.HRegionServer;
043import org.apache.hadoop.hbase.testclassification.SmallTests;
044import org.apache.hadoop.hbase.util.Bytes;
045import org.apache.hadoop.hbase.util.JVMClusterUtil.MasterThread;
046import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
047import org.junit.AfterClass;
048import org.junit.Assert;
049import org.junit.BeforeClass;
050import org.junit.ClassRule;
051import org.junit.Test;
052import org.junit.experimental.categories.Category;
053
054@Category(SmallTests.class)
055public class TestClientClusterMetrics {
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059      HBaseClassTestRule.forClass(TestClientClusterMetrics.class);
060
061  private static HBaseTestingUtility UTIL;
062  private static Admin ADMIN;
063  private final static int SLAVES = 5;
064  private final static int MASTERS = 3;
065  private static MiniHBaseCluster CLUSTER;
066  private static HRegionServer DEAD;
067  private static final TableName TABLE_NAME = TableName.valueOf("test");
068  private static final byte[] CF = Bytes.toBytes("cf");
069
070
071  @BeforeClass
072  public static void setUpBeforeClass() throws Exception {
073    Configuration conf = HBaseConfiguration.create();
074    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, MyObserver.class.getName());
075    UTIL = new HBaseTestingUtility(conf);
076    StartMiniClusterOption option = StartMiniClusterOption.builder()
077        .numMasters(MASTERS).numRegionServers(SLAVES).numDataNodes(SLAVES).build();
078    UTIL.startMiniCluster(option);
079    CLUSTER = UTIL.getHBaseCluster();
080    CLUSTER.waitForActiveAndReadyMaster();
081    ADMIN = UTIL.getAdmin();
082    // Kill one region server
083    List<RegionServerThread> rsts = CLUSTER.getLiveRegionServerThreads();
084    RegionServerThread rst = rsts.get(rsts.size() - 1);
085    DEAD = rst.getRegionServer();
086    DEAD.stop("Test dead servers metrics");
087    while (rst.isAlive()) {
088      Thread.sleep(500);
089    }
090  }
091
092  @Test
093  public void testDefaults() throws Exception {
094    ClusterMetrics origin = ADMIN.getClusterMetrics();
095    ClusterMetrics defaults = ADMIN.getClusterMetrics(EnumSet.allOf(Option.class));
096    Assert.assertEquals(origin.getHBaseVersion(), defaults.getHBaseVersion());
097    Assert.assertEquals(origin.getClusterId(), defaults.getClusterId());
098    Assert.assertEquals(origin.getAverageLoad(), defaults.getAverageLoad(), 0);
099    Assert.assertEquals(origin.getBackupMasterNames().size(),
100        defaults.getBackupMasterNames().size());
101    Assert.assertEquals(origin.getDeadServerNames().size(), defaults.getDeadServerNames().size());
102    Assert.assertEquals(origin.getRegionCount(), defaults.getRegionCount());
103    Assert.assertEquals(origin.getLiveServerMetrics().size(),
104        defaults.getLiveServerMetrics().size());
105    Assert.assertEquals(origin.getMasterInfoPort(), defaults.getMasterInfoPort());
106    Assert.assertEquals(origin.getServersName().size(), defaults.getServersName().size());
107    Assert.assertEquals(ADMIN.getRegionServers().size(), defaults.getServersName().size());
108  }
109
110  @Test
111  public void testAsyncClient() throws Exception {
112    try (AsyncConnection asyncConnect = ConnectionFactory.createAsyncConnection(
113      UTIL.getConfiguration()).get()) {
114      AsyncAdmin asyncAdmin = asyncConnect.getAdmin();
115      CompletableFuture<ClusterMetrics> originFuture =
116        asyncAdmin.getClusterMetrics();
117      CompletableFuture<ClusterMetrics> defaultsFuture =
118        asyncAdmin.getClusterMetrics(EnumSet.allOf(Option.class));
119      ClusterMetrics origin = originFuture.get();
120      ClusterMetrics defaults = defaultsFuture.get();
121      Assert.assertEquals(origin.getHBaseVersion(), defaults.getHBaseVersion());
122      Assert.assertEquals(origin.getClusterId(), defaults.getClusterId());
123      Assert.assertEquals(origin.getHBaseVersion(), defaults.getHBaseVersion());
124      Assert.assertEquals(origin.getClusterId(), defaults.getClusterId());
125      Assert.assertEquals(origin.getAverageLoad(), defaults.getAverageLoad(), 0);
126      Assert.assertEquals(origin.getBackupMasterNames().size(),
127        defaults.getBackupMasterNames().size());
128      Assert.assertEquals(origin.getDeadServerNames().size(), defaults.getDeadServerNames().size());
129      Assert.assertEquals(origin.getRegionCount(), defaults.getRegionCount());
130      Assert.assertEquals(origin.getLiveServerMetrics().size(),
131        defaults.getLiveServerMetrics().size());
132      Assert.assertEquals(origin.getMasterInfoPort(), defaults.getMasterInfoPort());
133      Assert.assertEquals(origin.getServersName().size(), defaults.getServersName().size());
134      origin.getTableRegionStatesCount().forEach(((tableName, regionStatesCount) -> {
135        RegionStatesCount defaultRegionStatesCount = defaults.getTableRegionStatesCount()
136          .get(tableName);
137        Assert.assertEquals(defaultRegionStatesCount, regionStatesCount);
138      }));
139    }
140  }
141
142  @Test
143  public void testLiveAndDeadServersStatus() throws Exception {
144    // Count the number of live regionservers
145    List<RegionServerThread> regionserverThreads = CLUSTER.getLiveRegionServerThreads();
146    int numRs = 0;
147    int len = regionserverThreads.size();
148    for (int i = 0; i < len; i++) {
149      if (regionserverThreads.get(i).isAlive()) {
150        numRs++;
151      }
152    }
153    // Depending on the (random) order of unit execution we may run this unit before the
154    // minicluster is fully up and recovered from the RS shutdown done during test init.
155    Waiter.waitFor(CLUSTER.getConfiguration(), 10 * 1000, 100, new Predicate<Exception>() {
156      @Override
157      public boolean evaluate() throws Exception {
158        ClusterMetrics metrics = ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS));
159        Assert.assertNotNull(metrics);
160        return metrics.getRegionCount() > 0;
161      }
162    });
163    // Retrieve live servers and dead servers info.
164    EnumSet<Option> options =
165        EnumSet.of(Option.LIVE_SERVERS, Option.DEAD_SERVERS, Option.SERVERS_NAME);
166    ClusterMetrics metrics = ADMIN.getClusterMetrics(options);
167    Assert.assertNotNull(metrics);
168    // exclude a dead region server
169    Assert.assertEquals(SLAVES -1, numRs);
170    // live servers = nums of regionservers
171    // By default, HMaster don't carry any regions so it won't report its load.
172    // Hence, it won't be in the server list.
173    Assert.assertEquals(numRs, metrics.getLiveServerMetrics().size());
174    Assert.assertTrue(metrics.getRegionCount() > 0);
175    Assert.assertNotNull(metrics.getDeadServerNames());
176    Assert.assertEquals(1, metrics.getDeadServerNames().size());
177    ServerName deadServerName = metrics.getDeadServerNames().iterator().next();
178    Assert.assertEquals(DEAD.getServerName(), deadServerName);
179    Assert.assertNotNull(metrics.getServersName());
180    Assert.assertEquals(numRs, metrics.getServersName().size());
181  }
182
183  @Test
184  public void testRegionStatesCount() throws Exception {
185    Table table = UTIL.createTable(TABLE_NAME, CF);
186    table.put(new Put(Bytes.toBytes("k1"))
187      .addColumn(CF, Bytes.toBytes("q1"), Bytes.toBytes("v1")));
188    table.put(new Put(Bytes.toBytes("k2"))
189      .addColumn(CF, Bytes.toBytes("q2"), Bytes.toBytes("v2")));
190    table.put(new Put(Bytes.toBytes("k3"))
191      .addColumn(CF, Bytes.toBytes("q3"), Bytes.toBytes("v3")));
192
193    ClusterMetrics metrics = ADMIN.getClusterMetrics();
194    Assert.assertEquals(metrics.getTableRegionStatesCount().size(), 3);
195    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
196      .getRegionsInTransition(), 0);
197    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
198      .getOpenRegions(), 1);
199    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
200      .getTotalRegions(), 1);
201    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
202      .getClosedRegions(), 0);
203    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
204      .getSplitRegions(), 0);
205    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
206      .getRegionsInTransition(), 0);
207    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
208      .getOpenRegions(), 1);
209    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
210      .getTotalRegions(), 1);
211
212    UTIL.deleteTable(TABLE_NAME);
213  }
214
215  @Test
216  public void testMasterAndBackupMastersStatus() throws Exception {
217    // get all the master threads
218    List<MasterThread> masterThreads = CLUSTER.getMasterThreads();
219    int numActive = 0;
220    int activeIndex = 0;
221    ServerName activeName = null;
222    HMaster active = null;
223    for (int i = 0; i < masterThreads.size(); i++) {
224      if (masterThreads.get(i).getMaster().isActiveMaster()) {
225        numActive++;
226        activeIndex = i;
227        active = masterThreads.get(activeIndex).getMaster();
228        activeName = active.getServerName();
229      }
230    }
231    Assert.assertNotNull(active);
232    Assert.assertEquals(1, numActive);
233    Assert.assertEquals(MASTERS, masterThreads.size());
234    // Retrieve master and backup masters infos only.
235    EnumSet<Option> options = EnumSet.of(Option.MASTER, Option.BACKUP_MASTERS);
236    ClusterMetrics metrics = ADMIN.getClusterMetrics(options);
237    Assert.assertTrue(metrics.getMasterName().equals(activeName));
238    Assert.assertEquals(MASTERS - 1, metrics.getBackupMasterNames().size());
239  }
240
241  @Test
242  public void testOtherStatusInfos() throws Exception {
243    EnumSet<Option> options =
244        EnumSet.of(Option.MASTER_COPROCESSORS, Option.HBASE_VERSION,
245                   Option.CLUSTER_ID, Option.BALANCER_ON);
246    ClusterMetrics metrics = ADMIN.getClusterMetrics(options);
247    Assert.assertEquals(1, metrics.getMasterCoprocessorNames().size());
248    Assert.assertNotNull(metrics.getHBaseVersion());
249    Assert.assertNotNull(metrics.getClusterId());
250    Assert.assertTrue(metrics.getAverageLoad() == 0.0);
251    Assert.assertNotNull(metrics.getBalancerOn());
252  }
253
254  @AfterClass
255  public static void tearDownAfterClass() throws Exception {
256    if (ADMIN != null) {
257      ADMIN.close();
258    }
259    UTIL.shutdownMiniCluster();
260  }
261
262  @Test
263  public void testObserver() throws IOException {
264    int preCount = MyObserver.PRE_COUNT.get();
265    int postCount = MyObserver.POST_COUNT.get();
266    Assert.assertTrue(ADMIN.getClusterMetrics().getMasterCoprocessorNames().stream()
267        .anyMatch(s -> s.equals(MyObserver.class.getSimpleName())));
268    Assert.assertEquals(preCount + 1, MyObserver.PRE_COUNT.get());
269    Assert.assertEquals(postCount + 1, MyObserver.POST_COUNT.get());
270  }
271
272  private static void insertData(final TableName tableName, int startRow, int rowCount)
273      throws IOException {
274    Table t = UTIL.getConnection().getTable(tableName);
275    Put p;
276    for (int i = 0; i < rowCount; i++) {
277      p = new Put(Bytes.toBytes("" + (startRow + i)));
278      p.addColumn(CF, Bytes.toBytes("val1"), Bytes.toBytes(i));
279      t.put(p);
280    }
281  }
282
283  public static class MyObserver implements MasterCoprocessor, MasterObserver {
284    private static final AtomicInteger PRE_COUNT = new AtomicInteger(0);
285    private static final AtomicInteger POST_COUNT = new AtomicInteger(0);
286
287    @Override public Optional<MasterObserver> getMasterObserver() {
288      return Optional.of(this);
289    }
290
291    @Override public void preGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx)
292        throws IOException {
293      PRE_COUNT.incrementAndGet();
294    }
295
296    @Override public void postGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx,
297        ClusterMetrics metrics) throws IOException {
298      POST_COUNT.incrementAndGet();
299    }
300  }
301}