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.atomic.AtomicInteger;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.ClusterMetrics.Option;
027import org.apache.hadoop.hbase.Waiter.Predicate;
028import org.apache.hadoop.hbase.client.Admin;
029import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
030import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
031import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
032import org.apache.hadoop.hbase.coprocessor.MasterObserver;
033import org.apache.hadoop.hbase.coprocessor.ObserverContext;
034import org.apache.hadoop.hbase.master.HMaster;
035import org.apache.hadoop.hbase.regionserver.HRegionServer;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.util.JVMClusterUtil.MasterThread;
038import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
039import org.junit.AfterClass;
040import org.junit.Assert;
041import org.junit.BeforeClass;
042import org.junit.ClassRule;
043import org.junit.Test;
044import org.junit.experimental.categories.Category;
045
046/**
047 * Test the ClusterStatus.
048 */
049@Category(MediumTests.class)
050public class TestClientClusterStatus {
051
052  @ClassRule
053  public static final HBaseClassTestRule CLASS_RULE =
054      HBaseClassTestRule.forClass(TestClientClusterStatus.class);
055
056  private static HBaseTestingUtility UTIL;
057  private static Admin ADMIN;
058  private final static int SLAVES = 5;
059  private final static int MASTERS = 3;
060  private static MiniHBaseCluster CLUSTER;
061  private static HRegionServer DEAD;
062
063  @BeforeClass
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 HBaseTestingUtility(conf);
068    StartMiniClusterOption option = StartMiniClusterOption.builder()
069        .numMasters(MASTERS).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    Assert.assertEquals(status0.getLiveServerMetrics().size(),
091        status1.getLiveServerMetrics().size());
092    checkPbObjectNotNull(new ClusterStatus(status0));
093    checkPbObjectNotNull(new ClusterStatus(status1));
094  }
095
096  @Test
097  public void testLiveAndDeadServersStatus() throws Exception {
098    // Count the number of live regionservers
099    List<RegionServerThread> regionserverThreads = CLUSTER.getLiveRegionServerThreads();
100    int numRs = 0;
101    int len = regionserverThreads.size();
102    for (int i = 0; i < len; i++) {
103      if (regionserverThreads.get(i).isAlive()) {
104        numRs++;
105      }
106    }
107    // Depending on the (random) order of unit execution we may run this unit before the
108    // minicluster is fully up and recovered from the RS shutdown done during test init.
109    Waiter.waitFor(CLUSTER.getConfiguration(), 10 * 1000, 100, new Predicate<Exception>() {
110      @Override
111      public boolean evaluate() throws Exception {
112        ClusterStatus status
113          = new ClusterStatus(ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)));
114        Assert.assertNotNull(status);
115        return status.getRegionsCount() > 0;
116      }
117    });
118    // Retrieve live servers and dead servers info.
119    EnumSet<Option> options =
120        EnumSet.of(Option.LIVE_SERVERS, Option.DEAD_SERVERS, Option.SERVERS_NAME);
121    ClusterStatus status = new ClusterStatus(ADMIN.getClusterMetrics(options));
122    checkPbObjectNotNull(status);
123    Assert.assertNotNull(status);
124    Assert.assertNotNull(status.getServers());
125    // exclude a dead region server
126    Assert.assertEquals(SLAVES -1, numRs);
127    // live servers = nums of regionservers
128    // By default, HMaster don't carry any regions so it won't report its load.
129    // Hence, it won't be in the server list.
130    Assert.assertEquals(status.getServers().size(), numRs);
131    Assert.assertTrue(status.getRegionsCount() > 0);
132    Assert.assertNotNull(status.getDeadServerNames());
133    Assert.assertEquals(1, status.getDeadServersSize());
134    ServerName deadServerName = status.getDeadServerNames().iterator().next();
135    Assert.assertEquals(DEAD.getServerName(), deadServerName);
136    Assert.assertNotNull(status.getServersName());
137    Assert.assertEquals(numRs, status.getServersName().size());
138  }
139
140  @Test
141  public void testMasterAndBackupMastersStatus() throws Exception {
142    // get all the master threads
143    List<MasterThread> masterThreads = CLUSTER.getMasterThreads();
144    int numActive = 0;
145    int activeIndex = 0;
146    ServerName activeName = null;
147    HMaster active = null;
148    for (int i = 0; i < masterThreads.size(); i++) {
149      if (masterThreads.get(i).getMaster().isActiveMaster()) {
150        numActive++;
151        activeIndex = i;
152        active = masterThreads.get(activeIndex).getMaster();
153        activeName = active.getServerName();
154      }
155    }
156    Assert.assertNotNull(active);
157    Assert.assertEquals(1, numActive);
158    Assert.assertEquals(MASTERS, masterThreads.size());
159    // Retrieve master and backup masters infos only.
160    EnumSet<Option> options = EnumSet.of(Option.MASTER, Option.BACKUP_MASTERS);
161    ClusterStatus status = new ClusterStatus(ADMIN.getClusterMetrics(options));
162    Assert.assertTrue(status.getMaster().equals(activeName));
163    Assert.assertEquals(MASTERS - 1, status.getBackupMastersSize());
164  }
165
166  @Test
167  public void testOtherStatusInfos() throws Exception {
168    EnumSet<Option> options =
169        EnumSet.of(Option.MASTER_COPROCESSORS, Option.HBASE_VERSION,
170                   Option.CLUSTER_ID, Option.BALANCER_ON);
171    ClusterStatus status = new ClusterStatus(ADMIN.getClusterMetrics(options));
172    Assert.assertTrue(status.getMasterCoprocessors().length == 1);
173    Assert.assertNotNull(status.getHBaseVersion());
174    Assert.assertNotNull(status.getClusterId());
175    Assert.assertTrue(status.getAverageLoad() == 0.0);
176    Assert.assertNotNull(status.getBalancerOn());
177  }
178
179  @AfterClass
180  public static void tearDownAfterClass() throws Exception {
181    if (ADMIN != null) ADMIN.close();
182    UTIL.shutdownMiniCluster();
183  }
184
185  @Test
186  public void testObserver() throws IOException {
187    int preCount = MyObserver.PRE_COUNT.get();
188    int postCount = MyObserver.POST_COUNT.get();
189    Assert.assertTrue(ADMIN.getClusterMetrics().getMasterCoprocessorNames().stream()
190      .anyMatch(s -> s.equals(MyObserver.class.getSimpleName())));
191    Assert.assertEquals(preCount + 1, MyObserver.PRE_COUNT.get());
192    Assert.assertEquals(postCount + 1, MyObserver.POST_COUNT.get());
193  }
194
195  /**
196   * HBASE-19496 do the refactor for ServerLoad and RegionLoad so the inner pb object is useless
197   * now. However, they are Public classes, and consequently we must make sure the all pb objects
198   * have initialized.
199   */
200  private static void checkPbObjectNotNull(ClusterStatus status) {
201    for (ServerName name : status.getLiveServerMetrics().keySet()) {
202      ServerLoad load = status.getLoad(name);
203      Assert.assertNotNull(load.obtainServerLoadPB());
204      for (RegionLoad rl : load.getRegionsLoad().values()) {
205        Assert.assertNotNull(rl.regionLoadPB);
206      }
207    }
208  }
209
210  public static class MyObserver implements MasterCoprocessor, MasterObserver {
211    private static final AtomicInteger PRE_COUNT = new AtomicInteger(0);
212    private static final AtomicInteger POST_COUNT = new AtomicInteger(0);
213
214    @Override public Optional<MasterObserver> getMasterObserver() {
215      return Optional.of(this);
216    }
217
218    @Override public void preGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx)
219        throws IOException {
220      PRE_COUNT.incrementAndGet();
221    }
222
223    @Override public void postGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx,
224      ClusterMetrics status) throws IOException {
225      POST_COUNT.incrementAndGet();
226    }
227  }
228}