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.client;
019
020import static org.apache.hadoop.hbase.client.RegionReplicaTestHelper.testLocator;
021import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasEnded;
022import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasKind;
023import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasName;
024import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasParentSpanId;
025import static org.hamcrest.MatcherAssert.assertThat;
026import static org.hamcrest.Matchers.allOf;
027import static org.hamcrest.Matchers.endsWith;
028import static org.hamcrest.Matchers.hasItem;
029
030import io.opentelemetry.api.trace.SpanKind;
031import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
032import io.opentelemetry.sdk.trace.data.SpanData;
033import java.io.IOException;
034import java.util.List;
035import java.util.concurrent.TimeUnit;
036import java.util.stream.Stream;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate;
039import org.apache.hadoop.hbase.HBaseTestingUtil;
040import org.apache.hadoop.hbase.HConstants;
041import org.apache.hadoop.hbase.HRegionLocation;
042import org.apache.hadoop.hbase.MatcherPredicate;
043import org.apache.hadoop.hbase.RegionLocations;
044import org.apache.hadoop.hbase.TableName;
045import org.apache.hadoop.hbase.client.RegionReplicaTestHelper.Locator;
046import org.apache.hadoop.hbase.client.trace.StringTraceRenderer;
047import org.apache.hadoop.hbase.security.User;
048import org.apache.hadoop.hbase.testclassification.ClientTests;
049import org.apache.hadoop.hbase.testclassification.MediumTests;
050import org.apache.hadoop.hbase.trace.TraceUtil;
051import org.hamcrest.Matcher;
052import org.junit.jupiter.api.AfterAll;
053import org.junit.jupiter.api.AfterEach;
054import org.junit.jupiter.api.BeforeAll;
055import org.junit.jupiter.api.BeforeEach;
056import org.junit.jupiter.api.Tag;
057import org.junit.jupiter.api.TestInfo;
058import org.junit.jupiter.api.TestTemplate;
059import org.junit.jupiter.api.extension.RegisterExtension;
060import org.junit.jupiter.params.provider.Arguments;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
065
066@Tag(MediumTests.TAG)
067@Tag(ClientTests.TAG)
068@HBaseParameterizedTestTemplate(name = "[{index}]: registry = {0}")
069public class TestAsyncMetaRegionLocator {
070
071  private static final Logger LOG = LoggerFactory.getLogger(TestAsyncMetaRegionLocator.class);
072
073  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
074
075  @RegisterExtension
076  private static final OpenTelemetryExtension OTEL_EXT = OpenTelemetryExtension.create();
077
078  private Class<? extends ConnectionRegistry> registryClass;
079
080  private ConnectionRegistry registry;
081
082  private AsyncMetaRegionLocator locator;
083
084  public TestAsyncMetaRegionLocator(Class<? extends ConnectionRegistry> registryClass) {
085    this.registryClass = registryClass;
086  }
087
088  @SuppressWarnings("deprecation")
089  public static Stream<Arguments> parameters() {
090    return Stream.of(Arguments.of(RpcConnectionRegistry.class), Arguments.of(MasterRegistry.class),
091      Arguments.of(ZKConnectionRegistry.class));
092  }
093
094  @BeforeAll
095  public static void setUpBeforeAll() throws Exception {
096    UTIL.startMiniCluster(3);
097    HBaseTestingUtil.setReplicas(UTIL.getAdmin(), TableName.META_TABLE_NAME, 3);
098    UTIL.waitUntilNoRegionsInTransition();
099    try (ConnectionRegistry registry =
100      ConnectionRegistryFactory.create(UTIL.getConfiguration(), User.getCurrent())) {
101      RegionReplicaTestHelper.waitUntilAllMetaReplicasAreReady(UTIL, registry);
102    }
103    UTIL.getAdmin().balancerSwitch(false, true);
104  }
105
106  @AfterAll
107  public static void tearDownAfterAll() throws IOException {
108    UTIL.shutdownMiniCluster();
109  }
110
111  @BeforeEach
112  public void setUp() throws IOException {
113    Configuration conf = new Configuration(UTIL.getConfiguration());
114    conf.setClass(HConstants.CLIENT_CONNECTION_REGISTRY_IMPL_CONF_KEY, registryClass,
115      ConnectionRegistry.class);
116    registry = ConnectionRegistryFactory.create(conf, User.getCurrent());
117    locator = new AsyncMetaRegionLocator(registry);
118  }
119
120  @AfterEach
121  public void tearDown() throws IOException {
122    Closeables.close(registry, true);
123  }
124
125  @TestTemplate
126  public void test(TestInfo testInfo) {
127    String methodName = testInfo.getTestMethod().get().getName();
128    TraceUtil.trace(() -> {
129      try {
130        testLocator(UTIL, TableName.META_TABLE_NAME, new Locator() {
131          @Override
132          public void updateCachedLocationOnError(HRegionLocation loc, Throwable error) {
133            locator.updateCachedLocationOnError(loc, error);
134          }
135
136          @Override
137          public RegionLocations getRegionLocations(TableName tableName, int replicaId,
138            boolean reload) throws Exception {
139            return locator.getRegionLocations(replicaId, reload).get();
140          }
141        });
142      } catch (Exception e) {
143        throw new RuntimeException(e);
144      }
145    }, methodName);
146
147    Matcher<SpanData> parentSpanMatcher = allOf(hasName(methodName), hasEnded());
148
149    UTIL.waitFor(TimeUnit.SECONDS.toMillis(5),
150      new MatcherPredicate<>(OTEL_EXT::getSpans, hasItem(parentSpanMatcher)));
151    List<SpanData> spans = OTEL_EXT.getSpans();
152    if (LOG.isDebugEnabled()) {
153      StringTraceRenderer renderer = new StringTraceRenderer(spans);
154      renderer.render(LOG::debug);
155    }
156    assertThat(spans, hasItem(parentSpanMatcher));
157    final SpanData parentSpan =
158      spans.stream().filter(parentSpanMatcher::matches).findAny().orElseThrow(AssertionError::new);
159
160    Matcher<SpanData> registryGetMetaRegionLocationsMatcher =
161      allOf(hasName(endsWith(registryClass.getSimpleName() + ".getMetaRegionLocations")),
162        hasParentSpanId(parentSpan), hasKind(SpanKind.INTERNAL), hasEnded());
163    assertThat(spans, hasItem(registryGetMetaRegionLocationsMatcher));
164
165    // RpcConnectionRegistry specific tracing assertions
166    if (registry instanceof RpcConnectionRegistry) {
167      SpanData registryGetMetaRegionLocationsSpan =
168        spans.stream().filter(registryGetMetaRegionLocationsMatcher::matches).findFirst()
169          .orElseThrow(AssertionError::new);
170      // we need to fetch cluster id at the first time
171      Matcher<SpanData> fetchClusterIdMatcher = allOf(
172        hasName(endsWith("ConnectionRegistryService/GetConnectionRegistry")),
173        hasParentSpanId(registryGetMetaRegionLocationsSpan), hasKind(SpanKind.CLIENT), hasEnded());
174      assertThat(spans, hasItem(fetchClusterIdMatcher));
175      SpanData fetchClusterIdSpan = spans.stream().filter(fetchClusterIdMatcher::matches)
176        .findFirst().orElseThrow(AssertionError::new);
177      Matcher<SpanData> clientGetMetaRegionLocationsMatcher =
178        allOf(hasName(endsWith("ClientMetaService/GetMetaRegionLocations")),
179          hasParentSpanId(fetchClusterIdSpan), hasKind(SpanKind.CLIENT), hasEnded());
180      assertThat(spans, hasItem(clientGetMetaRegionLocationsMatcher));
181    }
182  }
183}