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.procedure;
019
020import static io.opentelemetry.api.common.AttributeKey.longKey;
021import static org.apache.hadoop.hbase.client.trace.hamcrest.AttributesMatchers.containsEntry;
022import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasAttributes;
023import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasEnded;
024import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasName;
025import static org.apache.hadoop.hbase.client.trace.hamcrest.SpanDataMatchers.hasStatusWithCode;
026import static org.hamcrest.MatcherAssert.assertThat;
027import static org.hamcrest.Matchers.allOf;
028import static org.hamcrest.Matchers.any;
029import static org.hamcrest.Matchers.anyOf;
030import static org.hamcrest.Matchers.containsString;
031import static org.hamcrest.Matchers.hasItem;
032
033import io.opentelemetry.api.trace.StatusCode;
034import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
035import io.opentelemetry.sdk.trace.data.SpanData;
036import java.util.List;
037import java.util.concurrent.TimeUnit;
038import java.util.concurrent.atomic.AtomicReference;
039import java.util.stream.Collectors;
040import org.apache.hadoop.hbase.ConnectionExtension;
041import org.apache.hadoop.hbase.HRegionLocation;
042import org.apache.hadoop.hbase.MatcherPredicate;
043import org.apache.hadoop.hbase.MiniClusterExtension;
044import org.apache.hadoop.hbase.StartTestingClusterOption;
045import org.apache.hadoop.hbase.TableName;
046import org.apache.hadoop.hbase.Waiter;
047import org.apache.hadoop.hbase.client.AsyncAdmin;
048import org.apache.hadoop.hbase.client.AsyncConnection;
049import org.apache.hadoop.hbase.client.RegionInfo;
050import org.apache.hadoop.hbase.client.Table;
051import org.apache.hadoop.hbase.client.trace.StringTraceRenderer;
052import org.apache.hadoop.hbase.testclassification.MasterTests;
053import org.apache.hadoop.hbase.testclassification.MediumTests;
054import org.apache.hadoop.hbase.trace.TraceUtil;
055import org.apache.hadoop.hbase.util.Bytes;
056import org.hamcrest.Matcher;
057import org.hamcrest.Matchers;
058import org.junit.jupiter.api.BeforeEach;
059import org.junit.jupiter.api.Order;
060import org.junit.jupiter.api.Tag;
061import org.junit.jupiter.api.Test;
062import org.junit.jupiter.api.TestInfo;
063import org.junit.jupiter.api.extension.RegisterExtension;
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067/**
068 * Test of master ProcedureV2 tracing.
069 */
070@Tag(MasterTests.TAG)
071@Tag(MediumTests.TAG)
072public class TestProcedureTracing {
073  private static final Logger LOG = LoggerFactory.getLogger(TestProcedureTracing.class);
074
075  @RegisterExtension
076  @Order(1)
077  static final OpenTelemetryExtension otelExtension = OpenTelemetryExtension.create();
078
079  @RegisterExtension
080  @Order(2)
081  static final MiniClusterExtension miniClusterExtension = MiniClusterExtension.newBuilder()
082    .setMiniClusterOption(StartTestingClusterOption.builder().numWorkers(3).build()).build();
083
084  @RegisterExtension
085  @Order(3)
086  static final ConnectionExtension connectionExtension =
087    ConnectionExtension.createAsyncConnectionExtension(miniClusterExtension::createAsyncConnection);
088  private String testMethodName;
089
090  @BeforeEach
091  public void setTestMethod(TestInfo testInfo) {
092    testMethodName = testInfo.getTestMethod().get().getName();
093  }
094
095  @Test
096  public void testCreateOpenDeleteTableSpans() throws Exception {
097    final TableName tableName = TableName.valueOf(testMethodName);
098    final AsyncConnection conn = connectionExtension.getAsyncConnection();
099    final AsyncAdmin admin = conn.getAdmin();
100
101    final AtomicReference<List<String>> tableRegionsRef = new AtomicReference<>();
102    TraceUtil.trace(() -> {
103      try (final Table ignored = miniClusterExtension.getTestingUtility()
104        .createMultiRegionTable(tableName, Bytes.toBytes("fam"), 5)) {
105        final List<String> tableRegions = conn.getRegionLocator(tableName).getAllRegionLocations()
106          .get().stream().map(HRegionLocation::getRegion).map(RegionInfo::getEncodedName)
107          .collect(Collectors.toList());
108        tableRegionsRef.set(tableRegions);
109      }
110      if (admin.tableExists(tableName).get()) {
111        if (admin.isTableEnabled(tableName).get()) {
112          admin.disableTable(tableName).get();
113        }
114        admin.deleteTable(tableName).get();
115      }
116    }, testMethodName);
117
118    final Matcher<SpanData> testSpanMatcher = allOf(hasName(testMethodName), hasEnded());
119    Waiter.waitFor(conn.getConfiguration(), TimeUnit.MINUTES.toMillis(3),
120      new MatcherPredicate<>(otelExtension::getSpans, hasItem(testSpanMatcher)));
121    final List<SpanData> spans = otelExtension.getSpans();
122    final StringTraceRenderer renderer = new StringTraceRenderer(spans);
123    renderer.render(LOG::debug);
124
125    // Expect to find a span for a CreateTableProcedure for the test table
126    final Matcher<SpanData> createTableProcedureSpanMatcher = allOf(
127      hasName(
128        allOf(containsString("CreateTableProcedure"), containsString("table=" + testMethodName))),
129      hasEnded(), hasStatusWithCode(StatusCode.OK),
130      hasAttributes(allOf(containsEntry(longKey("procId"), any(Long.class)),
131        containsEntry(longKey("parentProcId"), any(Long.class)))));
132    assertThat("Expected to find a span for a CreateTableProcedure for the test table", spans,
133      hasItem(createTableProcedureSpanMatcher));
134
135    // Expect to find a span for a TransitRegionStateProcedure for the test table
136    final Matcher<SpanData> transitRegionStateProcedureSpanMatcher = allOf(
137      hasName(allOf(containsString("TransitRegionStateProcedure"),
138        containsString("table=" + testMethodName))),
139      hasEnded(), hasStatusWithCode(StatusCode.OK),
140      hasAttributes(allOf(containsEntry(longKey("procId"), any(Long.class)),
141        containsEntry(longKey("parentProcId"), any(Long.class)))));
142    assertThat("Expected to find a span for a TransitRegionStateProcedure for the test table",
143      spans, hasItem(transitRegionStateProcedureSpanMatcher));
144
145    // Expect to find a span for an OpenRegionProcedure for a region of the test table
146    final List<Matcher<? super String>> tableRegionMatchers =
147      tableRegionsRef.get().stream().map(Matchers::containsString).collect(Collectors.toList());
148    final Matcher<SpanData> openRegionProcedureSpanMatcher =
149      allOf(hasName(allOf(containsString("OpenRegionProcedure"), anyOf(tableRegionMatchers))),
150        hasEnded(), hasStatusWithCode(StatusCode.OK),
151        hasAttributes(allOf(containsEntry(longKey("procId"), any(Long.class)),
152          containsEntry(longKey("parentProcId"), any(Long.class)))));
153    assertThat("Expected to find a span for an OpenRegionProcedure for a region of the test table",
154      spans, hasItem(openRegionProcedureSpanMatcher));
155
156    // Expect to find a span for a CloseRegionProcedure for a region of the test table
157    final Matcher<SpanData> closeRegionProcedureSpanMatcher =
158      allOf(hasName(allOf(containsString("CloseRegionProcedure"), anyOf(tableRegionMatchers))),
159        hasEnded(), hasStatusWithCode(StatusCode.OK),
160        hasAttributes(allOf(containsEntry(longKey("procId"), any(Long.class)),
161          containsEntry(longKey("parentProcId"), any(Long.class)))));
162    assertThat("Expected to find a span for a CloseRegionProcedure for a region of the test table",
163      spans, hasItem(closeRegionProcedureSpanMatcher));
164
165    // Expect to find a span for a DisableTableProcedure for the test table
166    final Matcher<SpanData> disableTableProcedureSpanMatcher = allOf(
167      hasName(
168        allOf(containsString("DisableTableProcedure"), containsString("table=" + testMethodName))),
169      hasEnded(), hasStatusWithCode(StatusCode.OK),
170      hasAttributes(allOf(containsEntry(longKey("procId"), any(Long.class)),
171        containsEntry(longKey("parentProcId"), any(Long.class)))));
172    assertThat("Expected to find a span for a DisableTableProcedure for the test table", spans,
173      hasItem(disableTableProcedureSpanMatcher));
174
175    // Expect to find a span for a DeleteTableProcedure for the test table
176    final Matcher<SpanData> deleteTableProcedureSpanMatcher = allOf(
177      hasName(
178        allOf(containsString("DeleteTableProcedure"), containsString("table=" + testMethodName))),
179      hasEnded(), hasStatusWithCode(StatusCode.OK),
180      hasAttributes(allOf(containsEntry(longKey("procId"), any(Long.class)),
181        containsEntry(longKey("parentProcId"), any(Long.class)))));
182    assertThat("Expected to find a span for a DeleteTableProcedure for the test table", spans,
183      hasItem(deleteTableProcedureSpanMatcher));
184  }
185}