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