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.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.io.IOException;
025import java.util.EnumSet;
026import java.util.List;
027import java.util.Optional;
028import java.util.concurrent.atomic.AtomicInteger;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.ClusterMetrics.Option;
031import org.apache.hadoop.hbase.Waiter.Predicate;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
034import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
035import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
036import org.apache.hadoop.hbase.coprocessor.MasterObserver;
037import org.apache.hadoop.hbase.coprocessor.ObserverContext;
038import org.apache.hadoop.hbase.master.HMaster;
039import org.apache.hadoop.hbase.regionserver.HRegionServer;
040import org.apache.hadoop.hbase.testclassification.MediumTests;
041import org.apache.hadoop.hbase.testclassification.MiscTests;
042import org.apache.hadoop.hbase.util.JVMClusterUtil.MasterThread;
043import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
044import org.junit.jupiter.api.AfterAll;
045import org.junit.jupiter.api.BeforeAll;
046import org.junit.jupiter.api.Tag;
047import org.junit.jupiter.api.Test;
048
049/**
050 * Test the ClusterStatus.
051 */
052@Tag(MiscTests.TAG)
053@Tag(MediumTests.TAG)
054public class TestClientClusterStatus {
055
056  private static HBaseTestingUtil UTIL;
057  private static Admin ADMIN;
058  private final static int SLAVES = 5;
059  private final static int MASTERS = 3;
060  private static SingleProcessHBaseCluster CLUSTER;
061  private static HRegionServer DEAD;
062
063  @BeforeAll
064  public static void setUpBeforeClass() throws Exception {
065    Configuration conf = HBaseConfiguration.create();
066    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, MyObserver.class.getName());
067    UTIL = new HBaseTestingUtil(conf);
068    StartTestingClusterOption option = StartTestingClusterOption.builder().numMasters(MASTERS)
069      .numRegionServers(SLAVES).numDataNodes(SLAVES).build();
070    UTIL.startMiniCluster(option);
071    CLUSTER = UTIL.getHBaseCluster();
072    CLUSTER.waitForActiveAndReadyMaster();
073    ADMIN = UTIL.getAdmin();
074    // Kill one region server
075    List<RegionServerThread> rsts = CLUSTER.getLiveRegionServerThreads();
076    RegionServerThread rst = rsts.get(rsts.size() - 1);
077    DEAD = rst.getRegionServer();
078    DEAD.stop("Test dead servers status");
079    while (rst.isAlive()) {
080      Thread.sleep(500);
081    }
082  }
083
084  @Test
085  public void testNone() throws Exception {
086    ClusterMetrics status0 = ADMIN.getClusterMetrics(EnumSet.allOf(Option.class));
087    ClusterMetrics status1 = ADMIN.getClusterMetrics(EnumSet.noneOf(Option.class));
088    // Do a rough compare. More specific compares can fail because all regions not deployed yet
089    // or more requests than expected.
090    assertEquals(status0.getLiveServerMetrics().size(), status1.getLiveServerMetrics().size());
091  }
092
093  @Test
094  public void testLiveAndDeadServersStatus() throws Exception {
095    // Count the number of live regionservers
096    List<RegionServerThread> regionserverThreads = CLUSTER.getLiveRegionServerThreads();
097    int numRs = 0;
098    int len = regionserverThreads.size();
099    for (int i = 0; i < len; i++) {
100      if (regionserverThreads.get(i).isAlive()) {
101        numRs++;
102      }
103    }
104    // Depending on the (random) order of unit execution we may run this unit before the
105    // minicluster is fully up and recovered from the RS shutdown done during test init.
106    Waiter.waitFor(CLUSTER.getConfiguration(), 10 * 1000, 100, new Predicate<Exception>() {
107      @Override
108      public boolean evaluate() throws Exception {
109        ClusterMetrics status = ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS));
110        assertNotNull(status);
111        return status.getRegionCount() > 0;
112      }
113    });
114    // Retrieve live servers and dead servers info.
115    EnumSet<Option> options =
116      EnumSet.of(Option.LIVE_SERVERS, Option.DEAD_SERVERS, Option.SERVERS_NAME);
117    ClusterMetrics status = ADMIN.getClusterMetrics(options);
118    assertNotNull(status);
119    assertNotNull(status.getLiveServerMetrics().keySet());
120    // exclude a dead region server
121    assertEquals(SLAVES - 1, numRs);
122    // live servers = nums of regionservers
123    // By default, HMaster don't carry any regions so it won't report its load.
124    // Hence, it won't be in the server list.
125    assertEquals(status.getLiveServerMetrics().keySet().size(), numRs);
126    assertTrue(status.getRegionCount() > 0);
127    assertNotNull(status.getDeadServerNames());
128    assertEquals(1, status.getDeadServerNames().size());
129    ServerName deadServerName = status.getDeadServerNames().iterator().next();
130    assertEquals(DEAD.getServerName(), deadServerName);
131    assertNotNull(status.getServersName());
132    assertEquals(numRs, status.getServersName().size());
133  }
134
135  @Test
136  public void testMasterAndBackupMastersStatus() throws Exception {
137    // get all the master threads
138    List<MasterThread> masterThreads = CLUSTER.getMasterThreads();
139    int numActive = 0;
140    int activeIndex = 0;
141    ServerName activeName = null;
142    HMaster active = null;
143    for (int i = 0; i < masterThreads.size(); i++) {
144      if (masterThreads.get(i).getMaster().isActiveMaster()) {
145        numActive++;
146        activeIndex = i;
147        active = masterThreads.get(activeIndex).getMaster();
148        activeName = active.getServerName();
149      }
150    }
151    assertNotNull(active);
152    assertEquals(1, numActive);
153    assertEquals(MASTERS, masterThreads.size());
154    // Retrieve master and backup masters infos only.
155    EnumSet<Option> options = EnumSet.of(Option.MASTER, Option.BACKUP_MASTERS);
156    ClusterMetrics status = ADMIN.getClusterMetrics(options);
157    assertTrue(status.getMasterName().equals(activeName));
158    assertEquals(MASTERS - 1, status.getBackupMasterNames().size());
159  }
160
161  @Test
162  public void testOtherStatusInfos() throws Exception {
163    EnumSet<Option> options = EnumSet.of(Option.MASTER_COPROCESSORS, Option.HBASE_VERSION,
164      Option.CLUSTER_ID, Option.BALANCER_ON);
165    ClusterMetrics status = ADMIN.getClusterMetrics(options);
166    assertTrue(status.getMasterCoprocessorNames().size() == 1);
167    assertNotNull(status.getHBaseVersion());
168    assertNotNull(status.getClusterId());
169    assertTrue(status.getAverageLoad() == 0.0);
170    assertNotNull(status.getBalancerOn());
171  }
172
173  @AfterAll
174  public static void tearDownAfterClass() throws Exception {
175    if (ADMIN != null) ADMIN.close();
176    UTIL.shutdownMiniCluster();
177  }
178
179  @Test
180  public void testObserver() throws IOException {
181    int preCount = MyObserver.PRE_COUNT.get();
182    int postCount = MyObserver.POST_COUNT.get();
183    assertTrue(ADMIN.getClusterMetrics().getMasterCoprocessorNames().stream()
184      .anyMatch(s -> s.equals(MyObserver.class.getSimpleName())));
185    assertEquals(preCount + 1, MyObserver.PRE_COUNT.get());
186    assertEquals(postCount + 1, MyObserver.POST_COUNT.get());
187  }
188
189  public static class MyObserver implements MasterCoprocessor, MasterObserver {
190    private static final AtomicInteger PRE_COUNT = new AtomicInteger(0);
191    private static final AtomicInteger POST_COUNT = new AtomicInteger(0);
192
193    @Override
194    public Optional<MasterObserver> getMasterObserver() {
195      return Optional.of(this);
196    }
197
198    @Override
199    public void preGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx)
200      throws IOException {
201      PRE_COUNT.incrementAndGet();
202    }
203
204    @Override
205    public void postGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx,
206      ClusterMetrics status) throws IOException {
207      POST_COUNT.incrementAndGet();
208    }
209  }
210}