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.master.balancer; 019 020import static org.junit.jupiter.api.Assertions.assertTrue; 021 022import java.io.IOException; 023import java.util.HashSet; 024import java.util.Hashtable; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028import java.util.Random; 029import java.util.Set; 030import java.util.concurrent.ThreadLocalRandom; 031import javax.management.MBeanAttributeInfo; 032import javax.management.MBeanInfo; 033import javax.management.MBeanServerConnection; 034import javax.management.ObjectInstance; 035import javax.management.ObjectName; 036import javax.management.remote.JMXConnector; 037import javax.management.remote.JMXConnectorFactory; 038import org.apache.hadoop.conf.Configuration; 039import org.apache.hadoop.hbase.HBaseTestingUtil; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.JMXListener; 042import org.apache.hadoop.hbase.ServerName; 043import org.apache.hadoop.hbase.TableName; 044import org.apache.hadoop.hbase.client.RegionInfo; 045import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 046import org.apache.hadoop.hbase.testclassification.MediumTests; 047import org.apache.hadoop.hbase.testclassification.MiscTests; 048import org.apache.hadoop.hbase.util.Threads; 049import org.apache.hadoop.net.DNSToSwitchMapping; 050import org.junit.jupiter.api.AfterAll; 051import org.junit.jupiter.api.BeforeAll; 052import org.junit.jupiter.api.Disabled; 053import org.junit.jupiter.api.MethodOrderer; 054import org.junit.jupiter.api.Tag; 055import org.junit.jupiter.api.Test; 056import org.junit.jupiter.api.TestMethodOrder; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060@Tag(MiscTests.TAG) 061@Tag(MediumTests.TAG) 062@TestMethodOrder(MethodOrderer.MethodName.class) 063@Disabled 064public class TestStochasticBalancerJmxMetrics extends BalancerTestBase { 065 066 private static final Logger LOG = LoggerFactory.getLogger(TestStochasticBalancerJmxMetrics.class); 067 private static HBaseTestingUtil UTIL = new HBaseTestingUtil(); 068 private static int connectorPort = 61120; 069 private static StochasticLoadBalancer loadBalancer; 070 /** 071 * a simple cluster for testing JMX. 072 */ 073 private static int[] mockCluster_ensemble = new int[] { 0, 1, 2, 3 }; 074 private static int[] mockCluster_pertable_1 = new int[] { 0, 1, 2 }; 075 private static int[] mockCluster_pertable_2 = new int[] { 3, 1, 1 }; 076 private static int[] mockCluster_pertable_namespace = new int[] { 1, 3, 1 }; 077 078 private static final String TABLE_NAME_1 = "Table1"; 079 private static final String TABLE_NAME_2 = "Table2"; 080 private static final String TABLE_NAME_NAMESPACE = "hbase:namespace"; 081 082 private static Configuration conf = null; 083 084 /** 085 * Setup the environment for the test. 086 */ 087 @BeforeAll 088 public static void setupBeforeClass() throws Exception { 089 090 conf = UTIL.getConfiguration(); 091 092 conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, DNSToSwitchMapping.class); 093 conf.setFloat("hbase.regions.slop", 0.0f); 094 conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, JMXListener.class.getName()); 095 Random rand = ThreadLocalRandom.current(); 096 for (int i = 0; i < 10; i++) { 097 do { 098 int sign = i % 2 == 0 ? 1 : -1; 099 connectorPort += sign * rand.nextInt(100); 100 } while (!HBaseTestingUtil.available(connectorPort)); 101 try { 102 conf.setInt("regionserver.rmi.registry.port", connectorPort); 103 104 UTIL.startMiniCluster(); 105 break; 106 } catch (Exception e) { 107 LOG.debug("Encountered exception when starting cluster. Trying port " + connectorPort, e); 108 try { 109 // this is to avoid "IllegalStateException: A mini-cluster is already running" 110 UTIL.shutdownMiniCluster(); 111 } catch (Exception ex) { 112 LOG.debug("Encountered exception shutting down cluster", ex); 113 } 114 } 115 } 116 } 117 118 @AfterAll 119 public static void tearDownAfterClass() throws Exception { 120 UTIL.shutdownMiniCluster(); 121 } 122 123 /** 124 * In Ensemble mode, there should be only one ensemble table 125 */ 126 @Test 127 public void testJmxMetrics_EnsembleMode() throws Exception { 128 loadBalancer = new StochasticLoadBalancer(); 129 130 conf.setBoolean(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE, false); 131 loadBalancer.setClusterInfoProvider(new DummyClusterInfoProvider(conf)); 132 loadBalancer.initialize(); 133 134 TableName tableName = HConstants.ENSEMBLE_TABLE_NAME; 135 Map<ServerName, List<RegionInfo>> clusterState = mockClusterServers(mockCluster_ensemble); 136 loadBalancer.balanceTable(tableName, clusterState); 137 138 String[] tableNames = new String[] { tableName.getNameAsString() }; 139 String[] functionNames = loadBalancer.getCostFunctionNames(); 140 Set<String> jmxMetrics = readJmxMetricsWithRetry(); 141 Set<String> expectedMetrics = getExpectedJmxMetrics(tableNames, functionNames); 142 143 // printMetrics(jmxMetrics, "existing metrics in ensemble mode"); 144 // printMetrics(expectedMetrics, "expected metrics in ensemble mode"); 145 146 // assert that every expected is in the JMX 147 for (String expected : expectedMetrics) { 148 assertTrue(jmxMetrics.contains(expected), 149 "Metric " + expected + " can not be found in JMX in ensemble mode."); 150 } 151 } 152 153 /** 154 * In per-table mode, each table has a set of metrics 155 */ 156 @Test 157 public void testJmxMetrics_PerTableMode() throws Exception { 158 loadBalancer = new StochasticLoadBalancer(); 159 160 conf.setBoolean(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE, true); 161 loadBalancer.setClusterInfoProvider(new DummyClusterInfoProvider(conf)); 162 loadBalancer.initialize(); 163 164 // NOTE the size is normally set in setClusterMetrics, for test purpose, we set it manually 165 // Tables: hbase:namespace, table1, table2 166 // Functions: costFunctions, overall 167 String[] functionNames = loadBalancer.getCostFunctionNames(); 168 loadBalancer.updateMetricsSize(3 * (functionNames.length + 1)); 169 170 // table 1 171 TableName tableName = TableName.valueOf(TABLE_NAME_1); 172 Map<ServerName, List<RegionInfo>> clusterState = mockClusterServers(mockCluster_pertable_1); 173 loadBalancer.balanceTable(tableName, clusterState); 174 175 // table 2 176 tableName = TableName.valueOf(TABLE_NAME_2); 177 clusterState = mockClusterServers(mockCluster_pertable_2); 178 loadBalancer.balanceTable(tableName, clusterState); 179 180 // table hbase:namespace 181 tableName = TableName.valueOf(TABLE_NAME_NAMESPACE); 182 clusterState = mockClusterServers(mockCluster_pertable_namespace); 183 loadBalancer.balanceTable(tableName, clusterState); 184 185 String[] tableNames = new String[] { TABLE_NAME_1, TABLE_NAME_2, TABLE_NAME_NAMESPACE }; 186 Set<String> jmxMetrics = readJmxMetricsWithRetry(); 187 Set<String> expectedMetrics = getExpectedJmxMetrics(tableNames, functionNames); 188 189 // printMetrics(jmxMetrics, "existing metrics in per-table mode"); 190 // printMetrics(expectedMetrics, "expected metrics in per-table mode"); 191 192 // assert that every expected is in the JMX 193 for (String expected : expectedMetrics) { 194 assertTrue(jmxMetrics.contains(expected), 195 "Metric " + expected + " can not be found in JMX in per-table mode."); 196 } 197 } 198 199 private Set<String> readJmxMetricsWithRetry() throws IOException { 200 final int count = 0; 201 for (int i = 0; i < 10; i++) { 202 Set<String> metrics = readJmxMetrics(); 203 if (metrics != null) { 204 return metrics; 205 } 206 LOG.warn("Failed to get jmxmetrics... sleeping, retrying; " + i + " of " + count + " times"); 207 Threads.sleep(1000); 208 } 209 return null; 210 } 211 212 /** 213 * Read the attributes from Hadoop->HBase->Master->Balancer in JMX 214 */ 215 private Set<String> readJmxMetrics() throws IOException { 216 JMXConnector connector = null; 217 ObjectName target = null; 218 MBeanServerConnection mb = null; 219 try { 220 connector = 221 JMXConnectorFactory.connect(JMXListener.buildJMXServiceURL(connectorPort, connectorPort)); 222 mb = connector.getMBeanServerConnection(); 223 224 Hashtable<String, String> pairs = new Hashtable<>(); 225 pairs.put("service", "HBase"); 226 pairs.put("name", "Master"); 227 pairs.put("sub", "Balancer"); 228 target = new ObjectName("Hadoop", pairs); 229 MBeanInfo beanInfo = mb.getMBeanInfo(target); 230 231 Set<String> existingAttrs = new HashSet<>(); 232 for (MBeanAttributeInfo attrInfo : beanInfo.getAttributes()) { 233 existingAttrs.add(attrInfo.getName()); 234 } 235 return existingAttrs; 236 } catch (Exception e) { 237 LOG.warn("Failed to get bean!!! " + target, e); 238 if (mb != null) { 239 Set<ObjectInstance> instances = mb.queryMBeans(null, null); 240 Iterator<ObjectInstance> iterator = instances.iterator(); 241 System.out.println("MBean Found:"); 242 while (iterator.hasNext()) { 243 ObjectInstance instance = iterator.next(); 244 System.out.println("Class Name: " + instance.getClassName()); 245 System.out.println("Object Name: " + instance.getObjectName()); 246 } 247 } 248 } finally { 249 if (connector != null) { 250 try { 251 connector.close(); 252 } catch (Exception e) { 253 e.printStackTrace(); 254 } 255 } 256 } 257 return null; 258 } 259 260 /** 261 * Given the tables and functions, return metrics names that should exist in JMX 262 */ 263 private Set<String> getExpectedJmxMetrics(String[] tableNames, String[] functionNames) { 264 Set<String> ret = new HashSet<>(); 265 266 for (String tableName : tableNames) { 267 ret.add(StochasticLoadBalancer.composeAttributeName(tableName, "Overall")); 268 for (String functionName : functionNames) { 269 String metricsName = StochasticLoadBalancer.composeAttributeName(tableName, functionName); 270 ret.add(metricsName); 271 } 272 } 273 274 return ret; 275 } 276}