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;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.mockito.Mockito.mock;
024import static org.mockito.Mockito.verifyNoInteractions;
025import static org.mockito.Mockito.when;
026
027import java.io.IOException;
028import java.io.OutputStream;
029import java.nio.file.Files;
030import java.nio.file.Path;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.Set;
034import java.util.function.Consumer;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.hbase.CompatibilityFactory;
037import org.apache.hadoop.hbase.TableDescriptors;
038import org.apache.hadoop.hbase.client.MasterSwitchType;
039import org.apache.hadoop.hbase.master.snapshot.SnapshotCleanupStateStore;
040import org.apache.hadoop.hbase.metrics.MetricRegistries;
041import org.apache.hadoop.hbase.quotas.MasterQuotaManager;
042import org.apache.hadoop.hbase.quotas.QuotaUtil;
043import org.apache.hadoop.hbase.quotas.RpcThrottleStateStore;
044import org.apache.hadoop.hbase.test.MetricsAssertHelper;
045import org.apache.hadoop.hbase.testclassification.MasterTests;
046import org.apache.hadoop.hbase.testclassification.SmallTests;
047import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
048import org.junit.jupiter.api.AfterEach;
049import org.junit.jupiter.api.BeforeEach;
050import org.junit.jupiter.api.Tag;
051import org.junit.jupiter.api.Test;
052import org.junit.jupiter.api.io.TempDir;
053import org.junit.jupiter.params.ParameterizedTest;
054import org.junit.jupiter.params.provider.ValueSource;
055
056@Tag(MasterTests.TAG)
057@Tag(SmallTests.TAG)
058public class TestMasterSwitchMetrics {
059  private static final MetricsAssertHelper METRICS_HELPER =
060    CompatibilityFactory.getInstance(MetricsAssertHelper.class);
061
062  private HMaster master;
063  private Configuration conf;
064  private RpcThrottleStateStore rpcThrottleStateStore;
065  private MetricsMasterWrapperImpl wrapper;
066  private MetricsMasterSourceImpl source;
067
068  @BeforeEach
069  public void setUp() throws IOException {
070    master = mock(HMaster.class);
071    conf = new Configuration(false);
072    conf.setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
073    when(master.getConfiguration()).thenReturn(conf);
074    when(master.isActiveMaster()).thenReturn(true);
075    when(master.isInitialized()).thenReturn(true);
076    when(master.getMasterWalManager()).thenReturn(mock(MasterWalManager.class));
077    TableDescriptors tableDescriptors = mock(TableDescriptors.class);
078    when(tableDescriptors.exists(QuotaUtil.QUOTA_TABLE_NAME)).thenReturn(true);
079    when(master.getTableDescriptors()).thenReturn(tableDescriptors);
080    when(master.getClusterSchema()).thenReturn(mock(ClusterSchema.class));
081    MasterQuotaManager quotaManager = new MasterQuotaManager(master);
082    quotaManager.start();
083    when(master.getMasterQuotaManager()).thenReturn(quotaManager);
084    master.snapshotCleanupStateStore = mock(SnapshotCleanupStateStore.class);
085    rpcThrottleStateStore = mock(RpcThrottleStateStore.class);
086    when(master.getRpcThrottleStateStore()).thenReturn(rpcThrottleStateStore);
087    wrapper = new MetricsMasterWrapperImpl(master);
088    source = new MetricsMasterSourceImpl(wrapper);
089  }
090
091  @AfterEach
092  public void tearDown() {
093    DefaultMetricsSystem.instance().unregisterSource(MetricsMasterSource.METRICS_JMX_CONTEXT);
094    MetricRegistries.global().remove(source.getMetricRegistryInfo());
095  }
096
097  @Test
098  public void testEachSwitchChangesItsGauge() {
099    Map<String, Consumer<Boolean>> switches = new HashMap<>();
100    switches.put(MetricsMasterSource.BALANCER_ENABLED_NAME,
101      enabled -> when(master.isBalancerOn()).thenReturn(enabled));
102    switches.put(MetricsMasterSource.NORMALIZER_ENABLED_NAME,
103      enabled -> when(master.isNormalizerOn()).thenReturn(enabled));
104    switches.put(MetricsMasterSource.SPLIT_ENABLED_NAME,
105      enabled -> when(master.isSplitOrMergeEnabled(MasterSwitchType.SPLIT)).thenReturn(enabled));
106    switches.put(MetricsMasterSource.MERGE_ENABLED_NAME,
107      enabled -> when(master.isSplitOrMergeEnabled(MasterSwitchType.MERGE)).thenReturn(enabled));
108    switches.put(MetricsMasterSource.CLEANER_CHORE_ENABLED_NAME,
109      enabled -> when(master.isCleanerChoreEnabled()).thenReturn(enabled));
110    switches.put(MetricsMasterSource.CATALOG_JANITOR_ENABLED_NAME,
111      enabled -> when(master.isCatalogJanitorEnabled()).thenReturn(enabled));
112    switches.put(MetricsMasterSource.SNAPSHOT_CLEANUP_ENABLED_NAME,
113      enabled -> when(master.snapshotCleanupStateStore.get()).thenReturn(enabled));
114    switches.put(MetricsMasterSource.REPLICATION_PEER_MODIFICATION_ENABLED_NAME,
115      enabled -> when(master.isReplicationPeerModificationEnabled()).thenReturn(enabled));
116    switches.put(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME,
117      enabled -> when(rpcThrottleStateStore.get()).thenReturn(enabled));
118    Map<String, Integer> expected = new HashMap<>();
119    switches.keySet().forEach(name -> expected.put(name, 0));
120    for (Map.Entry<String, Consumer<Boolean>> entry : switches.entrySet()) {
121      for (boolean enabled : new boolean[] { true, false }) {
122        entry.getValue().accept(enabled);
123        expected.put(entry.getKey(), enabled ? 1 : 0);
124        assertEquals(expected, wrapper.getSwitchStates());
125        expected
126          .forEach((name, state) -> METRICS_HELPER.assertGauge(name, state.longValue(), source));
127      }
128    }
129  }
130
131  @Test
132  public void testOnlyActiveInitializedMasterExposesSwitches() {
133    Set<String> metricNames = wrapper.getSwitchStates().keySet();
134    when(master.isInitialized()).thenReturn(false);
135    assertTrue(wrapper.getSwitchStates().isEmpty());
136    metricNames.forEach(name -> assertFalse(METRICS_HELPER.checkGaugeExists(name, source)));
137    when(master.isInitialized()).thenReturn(true);
138    when(master.isActiveMaster()).thenReturn(false);
139    assertTrue(wrapper.getSwitchStates().isEmpty());
140    metricNames.forEach(name -> assertFalse(METRICS_HELPER.checkGaugeExists(name, source)));
141  }
142
143  @Test
144  public void testRpcThrottleOmittedUntilQuotaManagerReady() {
145    MasterQuotaManager quotaManager = master.getMasterQuotaManager();
146    Map<String, Integer> expected = new HashMap<>(wrapper.getSwitchStates());
147    expected.remove(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME);
148    when(master.getMasterQuotaManager()).thenReturn(null);
149    assertEquals(expected, wrapper.getSwitchStates());
150    assertFalse(
151      METRICS_HELPER.checkGaugeExists(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME, source));
152    expected.forEach((name, state) -> METRICS_HELPER.assertGauge(name, state.longValue(), source));
153
154    when(master.getMasterQuotaManager()).thenReturn(quotaManager);
155    when(rpcThrottleStateStore.get()).thenReturn(true);
156    expected.put(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME, 1);
157    assertEquals(expected, wrapper.getSwitchStates());
158    METRICS_HELPER.assertGauge(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME, 1L, source);
159  }
160
161  @Test
162  public void testRpcThrottleDisabledWithoutQuotaSupport() {
163    conf.setBoolean(QuotaUtil.QUOTA_CONF_KEY, false);
164    when(master.getMasterQuotaManager()).thenReturn(null);
165    when(rpcThrottleStateStore.get()).thenReturn(true);
166    assertEquals(0, wrapper.getSwitchStates().get(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME));
167    METRICS_HELPER.assertGauge(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME, 0L, source);
168    verifyNoInteractions(rpcThrottleStateStore);
169  }
170
171  @ParameterizedTest
172  @ValueSource(booleans = { false, true })
173  public void testRpcThrottleUnaffectedByConfigurationReload(boolean quotaEnabled,
174    @TempDir Path tempDir) throws IOException {
175    Path site = tempDir.resolve("hbase-site.xml");
176    writeQuotaConfiguration(site, quotaEnabled);
177    conf = new Configuration(false);
178    conf.addResource(new org.apache.hadoop.fs.Path(site.toUri()));
179    when(master.getConfiguration()).thenReturn(conf);
180    MasterQuotaManager quotaManager = new MasterQuotaManager(master);
181    quotaManager.start();
182    when(master.getMasterQuotaManager()).thenReturn(quotaManager);
183    when(rpcThrottleStateStore.get()).thenReturn(true);
184    long expected = quotaEnabled ? 1L : 0L;
185    assertEquals(quotaEnabled, quotaManager.isRpcThrottleEnabled());
186    METRICS_HELPER.assertGauge(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME, expected, source);
187
188    writeQuotaConfiguration(site, !quotaEnabled);
189    conf.reloadConfiguration();
190    assertEquals(!quotaEnabled, QuotaUtil.isQuotaEnabled(conf));
191    assertEquals(quotaEnabled, quotaManager.isRpcThrottleEnabled());
192    METRICS_HELPER.assertGauge(MetricsMasterSource.RPC_THROTTLE_ENABLED_NAME, expected, source);
193  }
194
195  private static void writeQuotaConfiguration(Path path, boolean quotaEnabled) throws IOException {
196    Configuration config = new Configuration(false);
197    config.setBoolean(QuotaUtil.QUOTA_CONF_KEY, quotaEnabled);
198    try (OutputStream out = Files.newOutputStream(path)) {
199      config.writeXml(out);
200    }
201  }
202}